Compare commits

..

3 Commits

23 changed files with 410 additions and 37 deletions

View File

@ -4,8 +4,7 @@ do_autopath()
import warnings, sys import warnings, sys
from py.test import raises from py.test import raises
from tiramisu import Config from tiramisu import Config, DomainnameOption, EmailOption, URLOption, OptionDescription
from tiramisu.option import DomainnameOption, EmailOption, URLOption, OptionDescription
from tiramisu.error import ValueWarning from tiramisu.error import ValueWarning
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.storage import list_sessions from tiramisu.storage import list_sessions
@ -19,9 +18,11 @@ def test_domainname():
d = DomainnameOption('d', '') d = DomainnameOption('d', '')
f = DomainnameOption('f', '', allow_without_dot=True) f = DomainnameOption('f', '', allow_without_dot=True)
g = DomainnameOption('g', '', allow_ip=True) g = DomainnameOption('g', '', allow_ip=True)
od = OptionDescription('a', '', [d, f, g]) h = DomainnameOption('h', '', allow_ip=True, cidr=True)
od = OptionDescription('a', '', [d, f, g, h])
cfg = Config(od) cfg = Config(od)
cfg.property.read_write() cfg.property.read_write()
#
cfg.option('d').value.set('toto.com') cfg.option('d').value.set('toto.com')
raises(ValueError, "cfg.option('d').value.set('toto')") raises(ValueError, "cfg.option('d').value.set('toto')")
cfg.option('d').value.set('toto3.com') cfg.option('d').value.set('toto3.com')
@ -40,9 +41,17 @@ def test_domainname():
cfg.option('f').value.set('d.t') cfg.option('f').value.set('d.t')
# #
raises(ValueError, "cfg.option('f').value.set('192.168.1.1')") raises(ValueError, "cfg.option('f').value.set('192.168.1.1')")
raises(ValueError, "cfg.option('f').value.set('192.168.1.0/24')")
#
cfg.option('g').value.set('toto.com') cfg.option('g').value.set('toto.com')
cfg.option('g').value.set('192.168.1.0') cfg.option('g').value.set('192.168.1.0')
cfg.option('g').value.set('192.168.1.29') cfg.option('g').value.set('192.168.1.29')
raises(ValueError, "cfg.option('g').value.set('192.168.1.0/24')")
#
cfg.option('h').value.set('toto.com')
raises(ValueError, "cfg.option('h').value.set('192.168.1.0')")
raises(ValueError, "cfg.option('h').value.set('192.168.1.29')")
cfg.option('h').value.set('192.168.1.0/24')
def test_domainname_upper(): def test_domainname_upper():

View File

@ -8,7 +8,7 @@ from tiramisu.config import KernelConfig
from tiramisu.setting import groups, owners from tiramisu.setting import groups, owners
from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \ from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription, SymLinkOption, IPOption, NetmaskOption, Leadership, \ StrOption, OptionDescription, SymLinkOption, IPOption, NetmaskOption, Leadership, \
undefined, Params, ParamOption, ParamValue, ParamContext undefined, Params, ParamOption, ParamValue, ParamContext, calc_value
from tiramisu.api import TIRAMISU_VERSION from tiramisu.api import TIRAMISU_VERSION
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
from tiramisu.i18n import _ from tiramisu.i18n import _
@ -1194,3 +1194,100 @@ def test_callback_raise():
api.option('od2.opt2').value.get() api.option('od2.opt2').value.get()
except ConfigError as err: except ConfigError as err:
assert '"Option 2"' in str(err) assert '"Option 2"' in str(err)
def test_calc_value_simple():
val1 = StrOption('val1', '', 'val1')
val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1)))
od = OptionDescription('root', '', [val1, val2])
cfg = Config(od)
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1'}
def test_calc_value_multi():
val1 = StrOption('val1', "", 'val1')
val2 = StrOption('val2', "", 'val2')
val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True)))
od = OptionDescription('root', '', [val1, val2, val3])
cfg = Config(od)
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']}
def test_calc_value_disabled():
val1 = StrOption('val1', '', 'val1')
val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True), default=ParamValue('default_value')))
od = OptionDescription('root', '', [val1, val2])
cfg = Config(od)
cfg.property.read_write()
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1'}
cfg.option('val1').property.add('disabled')
assert cfg.value.dict() == {'val2': 'default_value'}
def test_calc_value_condition():
boolean = BoolOption('boolean', '', True)
val1 = StrOption('val1', '', 'val1')
val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True),
default=ParamValue('default_value'),
condition=ParamOption(boolean),
expected=ParamValue(True)))
od = OptionDescription('root', '', [boolean, val1, val2])
cfg = Config(od)
cfg.property.read_write()
assert cfg.value.dict() == {'boolean': True, 'val1': 'val1', 'val2': 'val1'}
cfg.option('boolean').value.set(False)
assert cfg.value.dict() == {'boolean': False, 'val1': 'val1', 'val2': 'default_value'}
def test_calc_value_allow_none():
from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
val1 = StrOption('val1', "", 'val1')
val2 = StrOption('val2', "")
val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), allow_none=ParamValue(True)))
od = OptionDescription('root', '', [val1, val2, val3])
cfg = Config(od)
assert cfg.value.dict() == {'val1': 'val1', 'val2': None, 'val3': ['val1', None]}
def test_calc_value_remove_duplicate():
from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
val1 = StrOption('val1', "", 'val1')
val2 = StrOption('val2', "", 'val1')
val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), remove_duplicate_value=ParamValue(True)))
od = OptionDescription('root', '', [val1, val2, val3])
cfg = Config(od)
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
def test_calc_value_join():
from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
val1 = StrOption('val1', "", 'val1')
val2 = StrOption('val2', "", 'val2')
val3 = StrOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), join=ParamValue('.')))
od = OptionDescription('root', '', [val1, val2, val3])
cfg = Config(od)
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': 'val1.val2'}
def test_calc_value_min():
from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
val1 = StrOption('val1', "", 'val1')
val2 = StrOption('val2', "", 'val2')
val3 = StrOption('val3', "", 'val3')
val4 = StrOption('val4', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2), ParamOption(val3, True)), join=ParamValue('.'), min_args_len=ParamValue(3)))
od = OptionDescription('root', '', [val1, val2, val3, val4])
cfg = Config(od)
cfg.property.read_write()
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
cfg.option('val3').property.add('disabled')
assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val4': ''}
def test_calc_value_add():
from tiramisu import calc_value, IntOption, OptionDescription, Config, Params, ParamOption, ParamValue
val1 = IntOption('val1', "", 1)
val2 = IntOption('val2', "", 2)
val3 = IntOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), operator=ParamValue('add')))
od = OptionDescription('root', '', [val1, val2, val3])
cfg = Config(od)
assert cfg.value.dict() == {'val1': 1, 'val2': 2, 'val3': 3}

View File

@ -13,7 +13,7 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from .function import Params, ParamOption, ParamValue, ParamContext, \ from .function import Params, ParamOption, ParamValue, ParamContext, \
tiramisu_copy tiramisu_copy, calc_value
from .option import * from .option import *
from .error import APIError from .error import APIError
from .api import Config, MetaConfig, GroupConfig, MixConfig from .api import Config, MetaConfig, GroupConfig, MixConfig
@ -37,7 +37,8 @@ allfuncs = ['Params',
'Storage', 'Storage',
'list_sessions', 'list_sessions',
'delete_session', 'delete_session',
'tiramisu_copy'] 'tiramisu_copy',
'calc_value']
allfuncs.extend(all_options) allfuncs.extend(all_options)
del(all_options) del(all_options)
__all__ = tuple(allfuncs) __all__ = tuple(allfuncs)

View File

@ -148,7 +148,7 @@ class _TiramisuOptionOptionDescription(CommonTiramisuOption):
return self._option_bag.option return self._option_bag.option
def type(self): def type(self):
return self._option_bag.option.get_display_type() return self._option_bag.option.get_type()
def isleadership(self): def isleadership(self):
"""Test if option is a leader or a follower""" """Test if option is a leader or a follower"""

View File

@ -12,6 +12,9 @@
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Any, List, Optional
from operator import add, mul, sub, truediv
from .setting import undefined
from .i18n import _ from .i18n import _
@ -79,3 +82,235 @@ class ParamIndex(Param):
def tiramisu_copy(val): # pragma: no cover def tiramisu_copy(val): # pragma: no cover
return val return val
def calc_value(*args: List[Any],
multi: bool=False,
default: Any=undefined,
condition: Any=undefined,
expected: Any=undefined,
condition_operator: str='AND',
allow_none: bool=False,
remove_duplicate_value: bool=False,
join: Optional[str]=None,
min_args_len: Optional[int]=None,
operator: Optional[str]=None,
**kwargs) -> Any:
"""calculate value
:param multi: value returns must be a list of value
:param default: default value if condition is not matched or if args is empty
if there is more than one default value, set default_0, default_1, ...
:param condition: test if condition is equal to expected value
if there is more than one condition, set condition_0, condition_1, ...
:param expected: value expected for all conditions
if expected value is different between condition, set expected_0, expected_1, ...
:param condition_operator: OR or AND operator for condition
:param allow_none: if False, do not return list in None is present in list
:param remove_duplicate_value: if True, remote duplicated value
:param join: join all args with specified characters
:param min_args_len: if number of arguments is smaller than this value, return default value
:param operator: operator
examples:
* you want to copy value from an option to an other option:
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption
>>> val1 = StrOption('val1', '', 'val1')
>>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1)))
>>> od = OptionDescription('root', '', [val1, val2])
>>> cfg = Config(od)
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val1'}
* you want to copy values from two options in one multi option
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val2')
>>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True)))
>>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od)
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']}
* you want to copy a value from an option is it not disabled, otherwise set 'default_value'
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = StrOption('val1', '', 'val1')
>>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True), default=ParamValue('default_value')))
>>> od = OptionDescription('root', '', [val1, val2])
>>> cfg = Config(od)
>>> cfg.property.read_write()
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val1'}
>>> cfg.option('val1').property.add('disabled')
>>> cfg.value.dict()
{'val2': 'default_value'}
* you want to copy value from an option is an other is True, otherwise set 'default_value'
>>> from tiramisu import calc_value, BoolOption, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> boolean = BoolOption('boolean', '', True)
>>> val1 = StrOption('val1', '', 'val1')
>>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True),
... default=ParamValue('default_value'),
... condition=ParamOption(boolean),
... expected=ParamValue(True)))
>>> od = OptionDescription('root', '', [boolean, val1, val2])
>>> cfg = Config(od)
>>> cfg.property.read_write()
>>> cfg.value.dict()
{'boolean': True, 'val1': 'val1', 'val2': 'val1'}
>>> cfg.option('boolean').value.set(False)
>>> cfg.value.dict()
{'boolean': False, 'val1': 'val1', 'val2': 'default_value'}
* you want to copy option even if None is present
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "")
>>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), allow_none=ParamValue(True)))
>>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od)
>>> cfg.value.dict()
{'val1': 'val1', 'val2': None, 'val3': ['val1', None]}
* you want uniq value
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val1')
>>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), remove_duplicate_value=ParamValue(True)))
>>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od)
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
* you want to join two values with '.'
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val2')
>>> val3 = StrOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), join=ParamValue('.')))
>>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od)
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val2', 'val3': 'val1.val2'}
* you want join three values, only if almost three values are set
>>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = StrOption('val1', "", 'val1')
>>> val2 = StrOption('val2', "", 'val2')
>>> val3 = StrOption('val3', "", 'val3')
>>> val4 = StrOption('val4', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2), ParamOption(val3, True)), join=ParamValue('.'), min_args_len=ParamValue(3)))
>>> od = OptionDescription('root', '', [val1, val2, val3, val4])
>>> cfg = Config(od)
>>> cfg.property.read_write()
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
>>> cfg.option('val3').property.add('disabled')
>>> cfg.value.dict()
{'val1': 'val1', 'val2': 'val2', 'val4': ''}
* you want to add all values
>>> from tiramisu import calc_value, IntOption, OptionDescription, Config, Params, ParamOption, ParamValue
>>> val1 = IntOption('val1', "", 1)
>>> val2 = IntOption('val2', "", 2)
>>> val3 = IntOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), operator=ParamValue('add')))
>>> od = OptionDescription('root', '', [val1, val2, val3])
>>> cfg = Config(od)
>>> cfg.value.dict()
{'val1': 1, 'val2': 2, 'val3': 3}
"""
def value_from_kwargs(value: Any, pattern: str, to_dict: bool=False) -> Any:
# if value attribute exist return it's value
# otherwise pattern_0, pattern_1, ...
# otherwise undefined
if value is not undefined:
if to_dict == 'all':
returns = {0: value}
else:
returns = value
else:
kwargs_matches = {}
len_pattern = len(pattern)
for key in kwargs.keys():
if key.startswith(pattern):
index = int(key[len_pattern:])
kwargs_matches[index] = kwargs[key]
if not kwargs_matches:
return undefined
keys = sorted(kwargs_matches)
if to_dict:
returns = {}
else:
returns = []
for key in keys:
if to_dict:
returns[key] = kwargs_matches[key]
else:
returns.append(kwargs_matches[key])
return returns
def is_condition_matches():
calculated_conditions = value_from_kwargs(condition, 'condition_', to_dict='all')
if condition is not undefined:
is_matches = None
calculated_expected = value_from_kwargs(expected, 'expected_', to_dict=True)
for idx, calculated_condition in calculated_conditions.items():
if isinstance(calculated_expected, dict):
current_matches = calculated_condition == calculated_expected[idx]
else:
current_matches = calculated_condition == calculated_expected
if is_matches is None:
is_matches = current_matches
elif condition_operator == 'AND':
is_matches = is_matches and current_matches
elif condition_operator == 'OR':
is_matches = is_matches or current_matches
else:
raise ValueError(_('unexpected {} condition_operator in calc_value').format(condition_operator))
else:
is_matches = True
return is_matches
def get_value():
if not is_condition_matches():
# force to default
value = []
else:
value = list(args)
if min_args_len and not len(value) >= min_args_len:
value = []
if value == []:
# default value
new_default = value_from_kwargs(default, 'default_')
if new_default is not undefined:
if not isinstance(new_default, list):
value = [new_default]
else:
value = new_default
return value
value = get_value()
if not multi:
if join is not None:
value = join.join(value)
elif value and operator:
new_value = value[0]
op = {'mul': mul,
'add': add,
'div': truediv,
'sub': sub}[operator]
for val in value[1:]:
new_value = op(new_value, val)
value = new_value
elif value == []:
value = None
else:
value = value[0]
elif None in value and not allow_none:
value = []
elif remove_duplicate_value:
new_value = []
for val in value:
if val not in new_value:
new_value.append(val)
value = new_value
return value

View File

@ -27,6 +27,7 @@ from .option import Option
class BoolOption(Option): class BoolOption(Option):
"represents a choice between ``True`` and ``False``" "represents a choice between ``True`` and ``False``"
__slots__ = tuple() __slots__ = tuple()
_type = 'boolean'
_display_name = _('boolean') _display_name = _('boolean')
def _validate(self, def _validate(self,

View File

@ -28,6 +28,7 @@ from .option import Option
class BroadcastOption(Option): class BroadcastOption(Option):
__slots__ = tuple() __slots__ = tuple()
_type = 'broadcast_address'
_display_name = _('broadcast address') _display_name = _('broadcast address')
def _validate(self, def _validate(self,

View File

@ -33,6 +33,7 @@ class ChoiceOption(Option):
The option can also have the value ``None`` The option can also have the value ``None``
""" """
__slots__ = tuple() __slots__ = tuple()
_type = 'choice'
_display_name = _('choice') _display_name = _('choice')
def __init__(self, def __init__(self,

View File

@ -27,6 +27,7 @@ from .option import Option
class DateOption(Option): class DateOption(Option):
__slots__ = tuple() __slots__ = tuple()
_type = 'date'
_display_name = _('date') _display_name = _('date')
def _validate(self, def _validate(self,

View File

@ -19,15 +19,14 @@
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
import re import re
from ipaddress import ip_address, IPv4Address from ipaddress import ip_address
from ..setting import undefined, Undefined, OptionBag from ..setting import undefined, Undefined, OptionBag
from ..i18n import _ from ..i18n import _
from .option import Option from .option import Option
from .stroption import StrOption from .ipoption import IPOption
class DomainnameOption(StrOption): class DomainnameOption(IPOption):
"""represents the choice of a domain name """represents the choice of a domain name
netbios: for MS domain netbios: for MS domain
hostname: to identify the device hostname: to identify the device
@ -35,6 +34,7 @@ class DomainnameOption(StrOption):
fqdn: with tld, not supported yet fqdn: with tld, not supported yet
""" """
__slots__ = tuple() __slots__ = tuple()
_type = 'domainname'
_display_name = _('domain name') _display_name = _('domain name')
def __init__(self, def __init__(self,
@ -43,16 +43,17 @@ class DomainnameOption(StrOption):
default=None, default=None,
default_multi=None, default_multi=None,
requires=None, requires=None,
multi=False, multi: bool=False,
callback=None, callback=None,
callback_params=None, callback_params=None,
validator=None, validator=None,
validator_params=None, validator_params=None,
properties=None, properties=None,
allow_ip=False, allow_ip: bool=False,
type_='domainname', cidr: bool=False,
warnings_only=False, type_: str='domainname',
allow_without_dot=False): warnings_only: bool=False,
allow_without_dot=False) -> None:
if type_ not in ['netbios', 'hostname', 'domainname']: if type_ not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type_ {0} for hostname').format(type_)) raise ValueError(_('unknown type_ {0} for hostname').format(type_))
@ -72,25 +73,29 @@ class DomainnameOption(StrOption):
else: else:
regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type_)) regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type_))
if allow_ip: if allow_ip:
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) 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: else:
regexp = r'^{0}$'.format(regexp) regexp = r'^{0}$'.format(regexp)
extra['_domain_re'] = re.compile(regexp) extra['_domain_re'] = re.compile(regexp)
extra['_has_upper'] = re.compile('[A-Z]') extra['_has_upper'] = re.compile('[A-Z]')
super(DomainnameOption, self).__init__(name, super().__init__(name,
doc, doc,
default=default, default=default,
default_multi=default_multi, default_multi=default_multi,
callback=callback, callback=callback,
callback_params=callback_params, callback_params=callback_params,
requires=requires, requires=requires,
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_params=validator_params, validator_params=validator_params,
properties=properties, properties=properties,
warnings_only=warnings_only, warnings_only=warnings_only,
extra=extra) cidr=cidr,
_extra=extra)
def _get_len(self, type_): def _get_len(self, type_):
if type_ == 'netbios': if type_ == 'netbios':
@ -116,9 +121,10 @@ class DomainnameOption(StrOption):
except ValueError: except ValueError:
pass pass
else: else:
if self.impl_get_extra('_allow_ip') is True: if self.impl_get_extra('_allow_ip') is False:
return raise ValueError(_('must not be an IP'))
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')) part_name_length = self._get_len(self.impl_get_extra('_dom_type'))
if self.impl_get_extra('_dom_type') == 'domainname': if self.impl_get_extra('_dom_type') == 'domainname':
if not self.impl_get_extra('_allow_without_dot') and not "." in value: if not self.impl_get_extra('_allow_without_dot') and not "." in value:

View File

@ -28,4 +28,5 @@ class EmailOption(RegexpOption):
__slots__ = tuple() __slots__ = tuple()
#https://www.w3.org/TR/html-markup/input.email.html#input.email.attrs.value.single. #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-]+)*$") _regexp = re.compile(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
_type = 'email'
_display_name = _('email address') _display_name = _('email address')

View File

@ -27,4 +27,5 @@ from .stroption import RegexpOption
class FilenameOption(RegexpOption): class FilenameOption(RegexpOption):
__slots__ = tuple() __slots__ = tuple()
_regexp = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") _regexp = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
_type = 'filename'
_display_name = _('file name') _display_name = _('file name')

View File

@ -27,6 +27,7 @@ from .option import Option
class FloatOption(Option): class FloatOption(Option):
"represents a choice of a floating point number" "represents a choice of a floating point number"
__slots__ = tuple() __slots__ = tuple()
_type = 'float'
_display_name = _('float') _display_name = _('float')
def _validate(self, def _validate(self,

View File

@ -27,6 +27,7 @@ from .option import Option
class IntOption(Option): class IntOption(Option):
"represents a choice of an integer" "represents a choice of an integer"
__slots__ = tuple() __slots__ = tuple()
_type = 'integer'
_display_name = _('integer') _display_name = _('integer')
def __init__(self, def __init__(self,

View File

@ -32,6 +32,7 @@ from .networkoption import NetworkOption
class IPOption(StrOption): class IPOption(StrOption):
"represents the choice of an ip" "represents the choice of an ip"
__slots__ = tuple() __slots__ = tuple()
_type = 'ip'
_display_name = _('IP') _display_name = _('IP')
def __init__(self, def __init__(self,
@ -49,10 +50,15 @@ class IPOption(StrOption):
private_only=False, private_only=False,
allow_reserved=False, allow_reserved=False,
warnings_only=False, warnings_only=False,
cidr=False): cidr=False,
extra = {'_private_only': private_only, _extra=None):
'_allow_reserved': allow_reserved, if _extra is None:
'_cidr': cidr} extra = {}
else:
extra = _extra
extra['_private_only'] = private_only
extra['_allow_reserved'] = allow_reserved
extra['_cidr'] = cidr
super().__init__(name, super().__init__(name,
doc, doc,
default=default, default=default,

View File

@ -31,6 +31,7 @@ from .stroption import StrOption
class NetmaskOption(StrOption): class NetmaskOption(StrOption):
"represents the choice of a netmask" "represents the choice of a netmask"
__slots__ = tuple() __slots__ = tuple()
_type = 'netmask'
_display_name = _('netmask address') _display_name = _('netmask address')
def _validate(self, def _validate(self,

View File

@ -28,6 +28,7 @@ from .option import Option
class NetworkOption(Option): class NetworkOption(Option):
"represents the choice of a network" "represents the choice of a network"
__slots__ = tuple() __slots__ = tuple()
_type = 'network'
_display_name = _('network address') _display_name = _('network address')
def __init__(self, def __init__(self,

View File

@ -179,6 +179,10 @@ class Option(BaseOption):
def impl_is_dynsymlinkoption(self) -> bool: def impl_is_dynsymlinkoption(self) -> bool:
return False return False
def get_type(self) -> str:
# _display_name for compatibility with older version than 3.0rc3
return getattr(self, '_type', self._display_name)
def get_display_type(self) -> str: def get_display_type(self) -> str:
return self._display_name return self._display_name

View File

@ -28,6 +28,7 @@ from .stroption import StrOption
class PasswordOption(StrOption): class PasswordOption(StrOption):
"represents the choice of a password" "represents the choice of a password"
__slots__ = tuple() __slots__ = tuple()
_type = 'password'
_display_name = _('password') _display_name = _('password')
def _validate(self, def _validate(self,

View File

@ -40,6 +40,7 @@ class PortOption(StrOption):
""" """
__slots__ = tuple() __slots__ = tuple()
port_re = re.compile(r"^[0-9]*$") port_re = re.compile(r"^[0-9]*$")
_type = 'port'
_display_name = _('port') _display_name = _('port')
def __init__(self, def __init__(self,

View File

@ -29,6 +29,7 @@ from .option import Option
class StrOption(Option): class StrOption(Option):
"represents the choice of a string" "represents the choice of a string"
__slots__ = tuple() __slots__ = tuple()
_type = 'string'
_display_name = _('string') _display_name = _('string')
def _validate(self, def _validate(self,

View File

@ -30,6 +30,7 @@ class URLOption(DomainnameOption):
__slots__ = tuple() __slots__ = tuple()
proto_re = re.compile(r'(http|https)://') proto_re = re.compile(r'(http|https)://')
path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
_type = 'url'
_display_name = _('URL') _display_name = _('URL')
def _validate(self, def _validate(self,

View File

@ -28,4 +28,5 @@ class UsernameOption(RegexpOption):
__slots__ = tuple() __slots__ = tuple()
#regexp build with 'man 8 adduser' informations #regexp build with 'man 8 adduser' informations
_regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") _regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
_type = 'username'
_display_name = _('username') _display_name = _('username')