separate option in differents files
authorEmmanuel Garette <egarette@cadoles.com>
Mon, 24 Jul 2017 17:04:18 +0000 (19:04 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Mon, 24 Jul 2017 17:04:18 +0000 (19:04 +0200)
19 files changed:
tiramisu/option/__init__.py
tiramisu/option/booloption.py [new file with mode: 0644]
tiramisu/option/broadcastoption.py [new file with mode: 0644]
tiramisu/option/choiceoption.py [new file with mode: 0644]
tiramisu/option/dateoption.py [new file with mode: 0644]
tiramisu/option/domainnameoption.py [new file with mode: 0644]
tiramisu/option/emailoption.py [new file with mode: 0644]
tiramisu/option/filenameoption.py [new file with mode: 0644]
tiramisu/option/floatoption.py [new file with mode: 0644]
tiramisu/option/intoption.py [new file with mode: 0644]
tiramisu/option/ipoption.py [new file with mode: 0644]
tiramisu/option/netmaskoption.py [new file with mode: 0644]
tiramisu/option/networkoption.py [new file with mode: 0644]
tiramisu/option/option.py
tiramisu/option/passwordoption.py [new file with mode: 0644]
tiramisu/option/portoption.py [new file with mode: 0644]
tiramisu/option/stroption.py [new file with mode: 0644]
tiramisu/option/urloption.py [new file with mode: 0644]
tiramisu/option/usernameoption.py [new file with mode: 0644]

index a11cb64..2437d3d 100644 (file)
@@ -2,11 +2,23 @@ from .masterslave import MasterSlaves
 from .optiondescription import OptionDescription, DynOptionDescription, \
     SynDynOptionDescription
 from .baseoption import Option, SymLinkOption, DynSymLinkOption, submulti
-from .option import (ChoiceOption, BoolOption, IntOption, FloatOption,
-                     StrOption, UnicodeOption, IPOption, PortOption,
-                     NetworkOption, NetmaskOption, BroadcastOption,
-                     DomainnameOption, EmailOption, URLOption, UsernameOption,
-                     DateOption, FilenameOption, PasswordOption)
+from .choiceoption import ChoiceOption
+from .booloption import BoolOption
+from .intoption import IntOption
+from .floatoption import FloatOption
+from .stroption import StrOption, UnicodeOption
+from .ipoption import IPOption
+from .portoption import PortOption
+from .networkoption import NetworkOption
+from .netmaskoption import NetmaskOption
+from .broadcastoption import BroadcastOption
+from .domainnameoption import DomainnameOption
+from .emailoption import EmailOption
+from .urloption import URLOption
+from .usernameoption import UsernameOption
+from .dateoption import DateOption
+from .filenameoption import FilenameOption
+from .passwordoption import PasswordOption
 
 
 __all__ = ('MasterSlaves', 'OptionDescription', 'DynOptionDescription',
diff --git a/tiramisu/option/booloption.py b/tiramisu/option/booloption.py
new file mode 100644 (file)
index 0000000..ed6581f
--- /dev/null
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class BoolOption(Option):
+    "represents a choice between ``True`` and ``False``"
+    __slots__ = tuple()
+    _display_name = _('boolean')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        if not isinstance(value, bool):
+            return ValueError()
diff --git a/tiramisu/option/broadcastoption.py b/tiramisu/option/broadcastoption.py
new file mode 100644 (file)
index 0000000..175fd0e
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+from IPy import IP
+
+from ..error import ConfigError
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class BroadcastOption(Option):
+    __slots__ = tuple()
+    _display_name = _('broadcast address')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+        if value.count('.') != 3:
+            return ValueError()
+        for val in value.split('.'):
+            if val.startswith("0") and len(val) > 1:
+                return ValueError()
+        try:
+            IP('{0}/32'.format(value))
+        except ValueError:
+            return ValueError()
+
+    def _cons_broadcast(self, current_opt, opts, vals, warnings_only):
+        if len(vals) != 3:
+            raise ConfigError(_('invalid len for vals'))
+        if None in vals:
+            return
+        broadcast, network, netmask = vals
+        if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
+            return ValueError(_('broadcast {4} invalid with network {0}/{1} ({2}/{3})').format(
+                network, netmask, opts[1].impl_getname(), opts[2].impl_getname(), broadcast))
diff --git a/tiramisu/option/choiceoption.py b/tiramisu/option/choiceoption.py
new file mode 100644 (file)
index 0000000..eeffc53
--- /dev/null
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# 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 ..setting import undefined
+from ..i18n import _
+from .baseoption import Option, validate_callback, display_list
+from ..autolib import carry_out_calculation
+from ..error import ConfigError
+
+
+class ChoiceOption(Option):
+    """represents a choice out of several objects.
+
+    The option can also have the value ``None``
+    """
+    __slots__ = tuple()
+    _display_name = _('choice')
+
+    def __init__(self, name, doc, values, default=None,
+                 values_params=None, default_multi=None, requires=None,
+                 multi=False, callback=None, callback_params=None,
+                 validator=None, validator_params=None,
+                 properties=None, warnings_only=False):
+        """
+        :param values: is a list of values the option can possibly take
+        """
+        if isinstance(values, FunctionType):
+            validate_callback(values, values_params, 'values', self)
+        else:
+            if values_params is not None:
+                raise ValueError(_('values is not a function, so values_params must be None'))
+            if not isinstance(values, tuple):
+                raise TypeError(_('values must be a tuple or a function for {0}'
+                                 ).format(name))
+        self._choice_values = values
+        if values_params is not None:
+            self._choice_values_params = values_params
+        super(ChoiceOption, self).__init__(name, doc, default=default,
+                                           default_multi=default_multi,
+                                           callback=callback,
+                                           callback_params=callback_params,
+                                           requires=requires,
+                                           multi=multi,
+                                           validator=validator,
+                                           validator_params=validator_params,
+                                           properties=properties,
+                                           warnings_only=warnings_only)
+
+    def impl_get_values(self, context, current_opt=undefined):
+        if current_opt is undefined:
+            current_opt = self
+        #FIXME cache? but in context...
+        values = self._choice_values
+        if isinstance(values, FunctionType):
+            if context is None:
+                values = []
+            else:
+                values = carry_out_calculation(current_opt, context=context,
+                                               callback=values,
+                                               callback_params=getattr(self, '_choice_values_params', {}))
+                if isinstance(values, Exception):
+                    return values
+                if values is not undefined and not isinstance(values, list):
+                    raise ConfigError(_('calculated values for {0} is not a list'
+                                        '').format(self.impl_getname()))
+        return values
+
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        values = self.impl_get_values(context, current_opt=current_opt)
+        if isinstance(values, Exception):
+            return values
+        if values is not undefined and not value in values:
+            if len(values) == 1:
+                return ValueError(_('only {0} is allowed'
+                                    '').format(values[0]))
+            else:
+                return ValueError(_('only {0} are allowed'
+                                    '').format(display_list(values)))
diff --git a/tiramisu/option/dateoption.py b/tiramisu/option/dateoption.py
new file mode 100644 (file)
index 0000000..55c9304
--- /dev/null
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import re
+from datetime import datetime
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class DateOption(Option):
+    __slots__ = tuple()
+    _display_name = _('date')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+        try:
+            datetime.strptime(value, "%Y-%m-%d")
+        except ValueError:
+            return ValueError()
diff --git a/tiramisu/option/domainnameoption.py b/tiramisu/option/domainnameoption.py
new file mode 100644 (file)
index 0000000..765f5cb
--- /dev/null
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import re
+from IPy import IP
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class DomainnameOption(Option):
+    """represents the choice of a domain name
+    netbios: for MS domain
+    hostname: to identify the device
+    domainname:
+    fqdn: with tld, not supported yet
+    """
+    __slots__ = tuple()
+    _display_name = _('domain name')
+
+    def __init__(self, name, doc, default=None, default_multi=None,
+                 requires=None, multi=False, callback=None,
+                 callback_params=None, validator=None, validator_params=None,
+                 properties=None, allow_ip=False, type_='domainname',
+                 warnings_only=False, allow_without_dot=False):
+        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]:
+            raise ValueError(_('allow_ip must be a boolean'))
+        if allow_without_dot not in [True, False]:
+            raise ValueError(_('allow_without_dot must be a boolean'))
+        extra['_allow_ip'] = allow_ip
+        extra['_allow_without_dot'] = allow_without_dot
+        # FIXME should be
+        # regexp = r'^((?!-)[a-z0-9-]{1,63}(?<!-)\.{0,1})$'
+        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)
+        else:
+            regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type_))
+        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)
+        else:
+            regexp = r'^{0}$'.format(regexp)
+        extra['_domain_re'] = re.compile(regexp)
+        extra['_has_upper'] = re.compile('[A-Z]')
+
+        super(DomainnameOption, self).__init__(name, doc, default=default,
+                                               default_multi=default_multi,
+                                               callback=callback,
+                                               callback_params=callback_params,
+                                               requires=requires,
+                                               multi=multi,
+                                               validator=validator,
+                                               validator_params=validator_params,
+                                               properties=properties,
+                                               warnings_only=warnings_only,
+                                               extra=extra)
+
+    def _get_len(self, type_):
+        if type_ == 'netbios':
+            return 15
+        else:
+            return 63
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+
+        def _valid_length(val):
+            if len(val) < 1:
+                return ValueError(_("invalid length (min 1)"))
+            if len(val) > part_name_length:
+                return ValueError(_("invalid length (max {0})"
+                                    "").format(part_name_length))
+
+        if self._get_extra('_allow_ip') is True:
+            try:
+                IP('{0}/32'.format(value))
+                return
+            except ValueError:
+                pass
+        else:
+            try:
+                IP('{0}/32'.format(value))
+            except ValueError:
+                pass
+            else:
+                return ValueError(_('must not be an IP'))
+        part_name_length = self._get_len(self._get_extra('_dom_type'))
+        if self._get_extra('_dom_type') == 'domainname':
+            if not self._get_extra('_allow_without_dot') and not "." in value:
+                return ValueError(_("must have dot"))
+            if len(value) > 255:
+                return ValueError(_("invalid length (max 255)"))
+            for dom in value.split('.'):
+                err = _valid_length(dom)
+                if err:
+                    return err
+        else:
+            return _valid_length(value)
+
+    def _second_level_validation(self, value, warnings_only):
+        if self._get_extra('_has_upper').search(value):
+            return ValueError(_('some characters are uppercase'))
+        if not self._get_extra('_domain_re').search(value):
+            if warnings_only:
+                return ValueError(_('some characters may cause problems'))
+            else:
+                return ValueError()
diff --git a/tiramisu/option/emailoption.py b/tiramisu/option/emailoption.py
new file mode 100644 (file)
index 0000000..6070e0a
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import re
+
+from ..i18n import _
+from .option 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-]+)*$")
+    _display_name = _('email address')
diff --git a/tiramisu/option/filenameoption.py b/tiramisu/option/filenameoption.py
new file mode 100644 (file)
index 0000000..e849033
--- /dev/null
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import re
+
+from ..i18n import _
+from .option import _RegexpOption
+
+
+class FilenameOption(_RegexpOption):
+    __slots__ = tuple()
+    _regexp = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
+    _display_name = _('file name')
diff --git a/tiramisu/option/floatoption.py b/tiramisu/option/floatoption.py
new file mode 100644 (file)
index 0000000..2eea4fa
--- /dev/null
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class FloatOption(Option):
+    "represents a choice of a floating point number"
+    __slots__ = tuple()
+    _display_name = _('float')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        if not isinstance(value, float):
+            return ValueError()
diff --git a/tiramisu/option/intoption.py b/tiramisu/option/intoption.py
new file mode 100644 (file)
index 0000000..9ffcc51
--- /dev/null
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class IntOption(Option):
+    "represents a choice of an integer"
+    __slots__ = tuple()
+    _display_name = _('integer')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        if not isinstance(value, int):
+            return ValueError()
diff --git a/tiramisu/option/ipoption.py b/tiramisu/option/ipoption.py
new file mode 100644 (file)
index 0000000..346eaec
--- /dev/null
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+from IPy import IP
+
+from ..error import ConfigError
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class IPOption(Option):
+    "represents the choice of an ip"
+    __slots__ = tuple()
+    _display_name = _('IP')
+
+    def __init__(self, name, doc, default=None, default_multi=None,
+                 requires=None, multi=False, callback=None,
+                 callback_params=None, validator=None, validator_params=None,
+                 properties=None, private_only=False, allow_reserved=False,
+                 warnings_only=False):
+        extra = {'_private_only': private_only,
+                 '_allow_reserved': allow_reserved}
+        super(IPOption, self).__init__(name, doc, default=default,
+                                       default_multi=default_multi,
+                                       callback=callback,
+                                       callback_params=callback_params,
+                                       requires=requires,
+                                       multi=multi,
+                                       validator=validator,
+                                       validator_params=validator_params,
+                                       properties=properties,
+                                       warnings_only=warnings_only,
+                                       extra=extra)
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        # sometimes an ip term starts with a zero
+        # but this does not fit in some case, for example bind does not like it
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+        if value.count('.') != 3:
+            return ValueError()
+        for val in value.split('.'):
+            if val.startswith("0") and len(val) > 1:
+                return ValueError()
+        # 'standard' validation
+        try:
+            IP('{0}/32'.format(value))
+        except ValueError:
+            return ValueError()
+
+    def _second_level_validation(self, value, warnings_only):
+        ip = IP('{0}/32'.format(value))
+        if not self._get_extra('_allow_reserved') and ip.iptype() == 'RESERVED':
+            if warnings_only:
+                msg = _("shouldn't in reserved class")
+            else:
+                msg = _("mustn't be in reserved class")
+            return ValueError(msg)
+        if self._get_extra('_private_only') and ip.iptype() != 'PRIVATE':
+            if warnings_only:
+                msg = _("should be in private class")
+            else:
+                msg = _("must be in private class")
+            return ValueError(msg)
+
+    def _cons_in_network(self, current_opt, opts, vals, warnings_only):
+        if len(vals) != 3:
+            raise ConfigError(_('invalid len for vals'))
+        if None in vals:
+            return
+        ip, network, netmask = vals
+        if IP(ip) not in IP('{0}/{1}'.format(network, netmask)):
+            msg = _('{4} is not in network {0}/{1} ({2}/{3})')
+            return ValueError(msg.format(network, netmask,
+                                         opts[1].impl_getname(), opts[2].impl_getname(), ip))
+        # test if ip is not network/broadcast IP
+        return opts[2]._cons_ip_netmask(current_opt, (opts[2], opts[0]), (netmask, ip), warnings_only)
diff --git a/tiramisu/option/netmaskoption.py b/tiramisu/option/netmaskoption.py
new file mode 100644 (file)
index 0000000..daa65f6
--- /dev/null
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+from IPy import IP
+
+from ..error import ConfigError
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class NetmaskOption(Option):
+    "represents the choice of a netmask"
+    __slots__ = tuple()
+    _display_name = _('netmask address')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+        if value.count('.') != 3:
+            return ValueError()
+        for val in value.split('.'):
+            if val.startswith("0") and len(val) > 1:
+                return ValueError()
+        try:
+            IP('0.0.0.0/{0}'.format(value))
+        except ValueError:
+            return ValueError()
+
+    def _cons_network_netmask(self, current_opt, opts, vals, warnings_only):
+        #opts must be (netmask, network) options
+        if None in vals:
+            return
+        return self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only)
+
+    def _cons_ip_netmask(self, current_opt, opts, vals, warnings_only):
+        #opts must be (netmask, ip) options
+        if None in vals:
+            return
+        return self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only)
+
+    def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net,
+                       warnings_only):
+        if len(opts) != 2:
+            return ConfigError(_('invalid len for opts'))
+        msg = None
+        try:
+            ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
+                    make_net=make_net)
+            #if cidr == 32, ip same has network
+            if make_net and ip.prefixlen() != 32:
+                val_ip = IP(val_ipnetwork)
+                if ip.net() == val_ip:
+                    msg = _("this is a network with netmask {0} ({1})")
+                if ip.broadcast() == val_ip:
+                    msg = _("this is a broadcast with netmask {0} ({1})")
+
+        except ValueError:
+            if not make_net:
+                msg = _('with netmask {0} ({1})')
+        if msg is not None:
+            return ValueError(msg.format(val_netmask, opts[1].impl_getname()))
diff --git a/tiramisu/option/networkoption.py b/tiramisu/option/networkoption.py
new file mode 100644 (file)
index 0000000..f891c4c
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+from IPy import IP
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class NetworkOption(Option):
+    "represents the choice of a network"
+    __slots__ = tuple()
+    _display_name = _('network address')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+        if value.count('.') != 3:
+            return ValueError()
+        for val in value.split('.'):
+            if val.startswith("0") and len(val) > 1:
+                return ValueError()
+        try:
+            IP(value)
+        except ValueError:
+            return ValueError()
+
+    def _second_level_validation(self, value, warnings_only):
+        ip = IP(value)
+        if ip.iptype() == 'RESERVED':
+            if warnings_only:
+                msg = _("shouldn't be in reserved class")
+            else:
+                msg = _("mustn't be in reserved class")
+            return ValueError(msg)
index 5843ff3..9545e49 100644 (file)
 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
-import re
-import sys
-import datetime
-from IPy import IP
-from types import FunctionType
 from ..setting import undefined
 
-from ..error import ConfigError
-from ..i18n import _
-from .baseoption import Option, validate_callback, display_list
-from ..autolib import carry_out_calculation
-
-
-class ChoiceOption(Option):
-    """represents a choice out of several objects.
-
-    The option can also have the value ``None``
-    """
-    __slots__ = tuple()
-    _display_name = _('choice')
-
-    def __init__(self, name, doc, values, default=None,
-                 values_params=None, default_multi=None, requires=None,
-                 multi=False, callback=None, callback_params=None,
-                 validator=None, validator_params=None,
-                 properties=None, warnings_only=False):
-        """
-        :param values: is a list of values the option can possibly take
-        """
-        if isinstance(values, FunctionType):
-            validate_callback(values, values_params, 'values', self)
-        else:
-            if values_params is not None:
-                raise ValueError(_('values is not a function, so values_params must be None'))
-            if not isinstance(values, tuple):
-                raise TypeError(_('values must be a tuple or a function for {0}'
-                                  ).format(name))
-        self._choice_values = values
-        if values_params is not None:
-            self._choice_values_params = values_params
-        super(ChoiceOption, self).__init__(name, doc, default=default,
-                                           default_multi=default_multi,
-                                           callback=callback,
-                                           callback_params=callback_params,
-                                           requires=requires,
-                                           multi=multi,
-                                           validator=validator,
-                                           validator_params=validator_params,
-                                           properties=properties,
-                                           warnings_only=warnings_only)
-
-    def impl_get_values(self, context, current_opt=undefined):
-        if current_opt is undefined:
-            current_opt = self
-        #FIXME cache? but in context...
-        values = self._choice_values
-        if isinstance(values, FunctionType):
-            if context is None:
-                values = []
-            else:
-                values = carry_out_calculation(current_opt, context=context,
-                                               callback=values,
-                                               callback_params=getattr(self, '_choice_values_params', {}))
-                if isinstance(values, Exception):
-                    return values
-                if values is not undefined and not isinstance(values, list):
-                    raise ConfigError(_('calculated values for {0} is not a list'
-                                        '').format(self.impl_getname()))
-        return values
-
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        values = self.impl_get_values(context, current_opt=current_opt)
-        if isinstance(values, Exception):
-            return values
-        if values is not undefined and not value in values:
-            if len(values) == 1:
-                return ValueError(_('only {0} is allowed'
-                                    '').format(values[0]))
-            else:
-                return ValueError(_('only {0} are allowed'
-                                    '').format(display_list(values)))
-
-
-class BoolOption(Option):
-    "represents a choice between ``True`` and ``False``"
-    __slots__ = tuple()
-    _display_name = _('boolean')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        if not isinstance(value, bool):
-            return ValueError()
-
-
-class IntOption(Option):
-    "represents a choice of an integer"
-    __slots__ = tuple()
-    _display_name = _('integer')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        if not isinstance(value, int):
-            return ValueError()
-
-
-class FloatOption(Option):
-    "represents a choice of a floating point number"
-    __slots__ = tuple()
-    _display_name = _('float')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        if not isinstance(value, float):
-            return ValueError()
-
-
-class StrOption(Option):
-    "represents the choice of a string"
-    __slots__ = tuple()
-    _display_name = _('string')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        if not isinstance(value, str):
-            return ValueError()
-
-
-if sys.version_info[0] >= 3:  # pragma: no cover
-    #UnicodeOption is same as StrOption in python 3+
-    class UnicodeOption(StrOption):
-        __slots__ = tuple()
-        pass
-else:
-    class UnicodeOption(Option):
-        "represents the choice of a unicode string"
-        __slots__ = tuple()
-        _empty = u''
-        _display_name = _('unicode string')
-
-        def _validate(self, value, context=undefined, current_opt=undefined):
-            if not isinstance(value, unicode):
-                return ValueError()
-
-
-class PasswordOption(Option):
-    "represents the choice of a password"
-    __slots__ = tuple()
-    _display_name = _('password')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-
-
-class IPOption(Option):
-    "represents the choice of an ip"
-    __slots__ = tuple()
-    _display_name = _('IP')
-
-    def __init__(self, name, doc, default=None, default_multi=None,
-                 requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_params=None,
-                 properties=None, private_only=False, allow_reserved=False,
-                 warnings_only=False):
-        extra = {'_private_only': private_only,
-                 '_allow_reserved': allow_reserved}
-        super(IPOption, self).__init__(name, doc, default=default,
-                                       default_multi=default_multi,
-                                       callback=callback,
-                                       callback_params=callback_params,
-                                       requires=requires,
-                                       multi=multi,
-                                       validator=validator,
-                                       validator_params=validator_params,
-                                       properties=properties,
-                                       warnings_only=warnings_only,
-                                       extra=extra)
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        # sometimes an ip term starts with a zero
-        # but this does not fit in some case, for example bind does not like it
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-        if value.count('.') != 3:
-            return ValueError()
-        for val in value.split('.'):
-            if val.startswith("0") and len(val) > 1:
-                return ValueError()
-        # 'standard' validation
-        try:
-            IP('{0}/32'.format(value))
-        except ValueError:
-            return ValueError()
-
-    def _second_level_validation(self, value, warnings_only):
-        ip = IP('{0}/32'.format(value))
-        if not self._get_extra('_allow_reserved') and ip.iptype() == 'RESERVED':
-            if warnings_only:
-                msg = _("shouldn't in reserved class")
-            else:
-                msg = _("mustn't be in reserved class")
-            return ValueError(msg)
-        if self._get_extra('_private_only') and not ip.iptype() == 'PRIVATE':
-            if warnings_only:
-                msg = _("should be in private class")
-            else:
-                msg = _("must be in private class")
-            return ValueError(msg)
-
-    def _cons_in_network(self, current_opt, opts, vals, warnings_only):
-        if len(vals) != 3:
-            raise ConfigError(_('invalid len for vals'))
-        if None in vals:
-            return
-        ip, network, netmask = vals
-        if IP(ip) not in IP('{0}/{1}'.format(network, netmask)):
-            msg = _('{4} is not in network {0}/{1} ({2}/{3})')
-            return ValueError(msg.format(network, netmask,
-                              opts[1].impl_getname(), opts[2].impl_getname(), ip))
-        # test if ip is not network/broadcast IP
-        return opts[2]._cons_ip_netmask(current_opt, (opts[2], opts[0]), (netmask, ip), warnings_only)
-
-
-class PortOption(Option):
-    """represents the choice of a port
-    The port numbers are divided into three ranges:
-    the well-known ports,
-    the registered ports,
-    and the dynamic or private ports.
-    You can actived this three range.
-    Port number 0 is reserved and can't be used.
-    see: http://en.wikipedia.org/wiki/Port_numbers
-    """
-    __slots__ = tuple()
-    port_re = re.compile(r"^[0-9]*$")
-    _display_name = _('port')
-
-    def __init__(self, name, doc, default=None, default_multi=None,
-                 requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_params=None,
-                 properties=None, allow_range=False, allow_zero=False,
-                 allow_wellknown=True, allow_registred=True,
-                 allow_private=False, warnings_only=False):
-        extra = {'_allow_range': allow_range,
-                 '_min_value': None,
-                 '_max_value': None}
-        ports_min = [0, 1, 1024, 49152]
-        ports_max = [0, 1023, 49151, 65535]
-        is_finally = False
-        for index, allowed in enumerate([allow_zero,
-                                         allow_wellknown,
-                                         allow_registred,
-                                         allow_private]):
-            if extra['_min_value'] is None:
-                if allowed:
-                    extra['_min_value'] = ports_min[index]
-            elif not allowed:
-                is_finally = True
-            elif allowed and is_finally:
-                raise ValueError(_('inconsistency in allowed range'))
-            if allowed:
-                extra['_max_value'] = ports_max[index]
-
-        if extra['_max_value'] is None:
-            raise ValueError(_('max value is empty'))
-
-        super(PortOption, self).__init__(name, doc, default=default,
-                                         default_multi=default_multi,
-                                         callback=callback,
-                                         callback_params=callback_params,
-                                         requires=requires,
-                                         multi=multi,
-                                         validator=validator,
-                                         validator_params=validator_params,
-                                         properties=properties,
-                                         warnings_only=warnings_only,
-                                         extra=extra)
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        if isinstance(value, int):
-            if sys.version_info[0] >= 3:  # pragma: no cover
-                value = str(value)
-            else:
-                value = unicode(value)
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-        if self._get_extra('_allow_range') and ":" in str(value):
-            value = str(value).split(':')
-            if len(value) != 2:
-                return ValueError(_('range must have two values only'))
-            if not value[0] < value[1]:
-                return ValueError(_('first port in range must be'
-                                  ' smaller than the second one'))
-        else:
-            value = [value]
-
-        for val in value:
-            if not self.port_re.search(val):
-                return ValueError()
-            val = int(val)
-            if not self._get_extra('_min_value') <= val <= self._get_extra('_max_value'):
-                return ValueError(_('must be an integer between {0} '
-                                    'and {1}').format(self._get_extra('_min_value'),
-                                                      self._get_extra('_max_value')))
-
-
-class NetworkOption(Option):
-    "represents the choice of a network"
-    __slots__ = tuple()
-    _display_name = _('network address')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-        if value.count('.') != 3:
-            return ValueError()
-        for val in value.split('.'):
-            if val.startswith("0") and len(val) > 1:
-                return ValueError()
-        try:
-            IP(value)
-        except ValueError:
-            return ValueError()
-
-    def _second_level_validation(self, value, warnings_only):
-        ip = IP(value)
-        if ip.iptype() == 'RESERVED':
-            if warnings_only:
-                msg = _("shouldn't be in reserved class")
-            else:
-                msg = _("mustn't be in reserved class")
-            return ValueError(msg)
-
-
-class NetmaskOption(Option):
-    "represents the choice of a netmask"
-    __slots__ = tuple()
-    _display_name = _('netmask address')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-        if value.count('.') != 3:
-            return ValueError()
-        for val in value.split('.'):
-            if val.startswith("0") and len(val) > 1:
-                return ValueError()
-        try:
-            IP('0.0.0.0/{0}'.format(value))
-        except ValueError:
-            return ValueError()
-
-    def _cons_network_netmask(self, current_opt, opts, vals, warnings_only):
-        #opts must be (netmask, network) options
-        if None in vals:
-            return
-        return self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only)
-
-    def _cons_ip_netmask(self, current_opt, opts, vals, warnings_only):
-        #opts must be (netmask, ip) options
-        if None in vals:
-            return
-        return self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only)
-
-    def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net,
-                       warnings_only):
-        if len(opts) != 2:
-            return ConfigError(_('invalid len for opts'))
-        msg = None
-        try:
-            ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
-                    make_net=make_net)
-            #if cidr == 32, ip same has network
-            if make_net and ip.prefixlen() != 32:
-                val_ip = IP(val_ipnetwork)
-                if ip.net() == val_ip:
-                    msg = _("this is a network with netmask {0} ({1})")
-                if ip.broadcast() == val_ip:
-                    msg = _("this is a broadcast with netmask {0} ({1})")
-
-        except ValueError:
-            if not make_net:
-                msg = _('with netmask {0} ({1})')
-        if msg is not None:
-            return ValueError(msg.format(val_netmask, opts[1].impl_getname()))
-
-
-class BroadcastOption(Option):
-    __slots__ = tuple()
-    _display_name = _('broadcast address')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-        if value.count('.') != 3:
-            return ValueError()
-        for val in value.split('.'):
-            if val.startswith("0") and len(val) > 1:
-                return ValueError()
-        try:
-            IP('{0}/32'.format(value))
-        except ValueError:
-            return ValueError()
-
-    def _cons_broadcast(self, current_opt, opts, vals, warnings_only):
-        if len(vals) != 3:
-            raise ConfigError(_('invalid len for vals'))
-        if None in vals:
-            return
-        broadcast, network, netmask = vals
-        if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
-            return ValueError(_('broadcast {4} invalid with network {0}/{1} ({2}/{3})').format(
-                network, netmask, opts[1].impl_getname(), opts[2].impl_getname(), broadcast))
-
-
-class DomainnameOption(Option):
-    """represents the choice of a domain name
-    netbios: for MS domain
-    hostname: to identify the device
-    domainname:
-    fqdn: with tld, not supported yet
-    """
-    __slots__ = tuple()
-    _display_name = _('domain name')
-
-    def __init__(self, name, doc, default=None, default_multi=None,
-                 requires=None, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_params=None,
-                 properties=None, allow_ip=False, type_='domainname',
-                 warnings_only=False, allow_without_dot=False):
-        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]:
-            raise ValueError(_('allow_ip must be a boolean'))
-        if allow_without_dot not in [True, False]:
-            raise ValueError(_('allow_without_dot must be a boolean'))
-        extra['_allow_ip'] = allow_ip
-        extra['_allow_without_dot'] = allow_without_dot
-        # FIXME should be
-        # regexp = r'^((?!-)[a-z0-9-]{1,63}(?<!-)\.{0,1})$'
-        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)
-        else:
-            regexp = r'((?!-)[a-z0-9-]{{1,{0}}})'.format(self._get_len(type_))
-        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)
-        else:
-            regexp = r'^{0}$'.format(regexp)
-        extra['_domain_re'] = re.compile(regexp)
-        extra['_has_upper'] = re.compile('[A-Z]')
-
-        super(DomainnameOption, self).__init__(name, doc, default=default,
-                                               default_multi=default_multi,
-                                               callback=callback,
-                                               callback_params=callback_params,
-                                               requires=requires,
-                                               multi=multi,
-                                               validator=validator,
-                                               validator_params=validator_params,
-                                               properties=properties,
-                                               warnings_only=warnings_only,
-                                               extra=extra)
-
-    def _get_len(self, type_):
-        if type_ == 'netbios':
-            return 15
-        else:
-            return 63
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-
-        def _valid_length(val):
-            if len(val) < 1:
-                return ValueError(_("invalid length (min 1)"))
-            if len(val) > part_name_length:
-                return ValueError(_("invalid length (max {0})"
-                                    "").format(part_name_length))
-
-        if self._get_extra('_allow_ip') is True:
-            try:
-                IP('{0}/32'.format(value))
-                return
-            except ValueError:
-                pass
-        else:
-            try:
-                IP('{0}/32'.format(value))
-            except ValueError:
-                pass
-            else:
-                return ValueError(_('must not be an IP'))
-        part_name_length = self._get_len(self._get_extra('_dom_type'))
-        if self._get_extra('_dom_type') == 'domainname':
-            if not self._get_extra('_allow_without_dot') and not "." in value:
-                return ValueError(_("must have dot"))
-            if len(value) > 255:
-                return ValueError(_("invalid length (max 255)"))
-            for dom in value.split('.'):
-                err = _valid_length(dom)
-                if err:
-                    return err
-        else:
-            return _valid_length(value)
-
-    def _second_level_validation(self, value, warnings_only):
-        if self._get_extra('_has_upper').search(value):
-            return ValueError(_('some characters are uppercase'))
-        if not self._get_extra('_domain_re').search(value):
-            if warnings_only:
-                return ValueError(_('some characters may cause problems'))
-            else:
-                return ValueError()
-
-
-class URLOption(DomainnameOption):
-    __slots__ = tuple()
-    proto_re = re.compile(r'(http|https)://')
-    path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
-    _display_name = _('URL')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-        match = self.proto_re.search(value)
-        if not match:
-            return ValueError(_('must start with http:// or '
-                                'https://'))
-        value = value[len(match.group(0)):]
-        # get domain/files
-        splitted = value.split('/', 1)
-        if len(splitted) == 1:
-            domain = value
-            files = None
-        else:
-            domain, files = splitted
-        # if port in domain
-        splitted = domain.split(':', 1)
-        if len(splitted) == 1:
-            domain = splitted[0]
-            port = 0
-        else:
-            domain, port = splitted
-        if not 0 <= int(port) <= 65535:
-            return ValueError(_('port must be an between 0 and '
-                                '65536'))
-        # validate domainname
-        err = super(URLOption, self)._validate(domain)
-        if err:
-            return err
-        err = super(URLOption, self)._second_level_validation(domain, False)
-        if err:
-            return err
-        # validate file
-        if files is not None and files != '' and not self.path_re.search(files):
-            return ValueError(_('must ends with a valid resource name'))
-
-    def _second_level_validation(self, value, warnings_only):
-        pass
+from .baseoption import Option
 
 
 class _RegexpOption(Option):
     __slots__ = tuple()
+
     def _validate(self, value, context=undefined, current_opt=undefined):
         err = self._impl_valid_unicode(value)
         if err:
@@ -600,37 +34,3 @@ class _RegexpOption(Option):
         match = self._regexp.search(value)
         if not match:
             return ValueError()
-
-
-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-]+)*$")
-    _display_name = _('email address')
-
-
-class UsernameOption(_RegexpOption):
-    __slots__ = tuple()
-    #regexp build with 'man 8 adduser' informations
-    _regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
-    _display_name = _('username')
-
-
-class FilenameOption(_RegexpOption):
-    __slots__ = tuple()
-    _regexp = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
-    _display_name = _('file name')
-
-
-class DateOption(Option):
-    __slots__ = tuple()
-    _display_name = _('date')
-
-    def _validate(self, value, context=undefined, current_opt=undefined):
-        err = self._impl_valid_unicode(value)
-        if err:
-            return err
-        try:
-            datetime.datetime.strptime(value,"%Y-%m-%d")
-        except ValueError:
-            return ValueError()
diff --git a/tiramisu/option/passwordoption.py b/tiramisu/option/passwordoption.py
new file mode 100644 (file)
index 0000000..57e5a2d
--- /dev/null
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class PasswordOption(Option):
+    "represents the choice of a password"
+    __slots__ = tuple()
+    _display_name = _('password')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
diff --git a/tiramisu/option/portoption.py b/tiramisu/option/portoption.py
new file mode 100644 (file)
index 0000000..db851b0
--- /dev/null
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import re
+import sys
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class PortOption(Option):
+    """represents the choice of a port
+    The port numbers are divided into three ranges:
+    the well-known ports,
+    the registered ports,
+    and the dynamic or private ports.
+    You can actived this three range.
+    Port number 0 is reserved and can't be used.
+    see: http://en.wikipedia.org/wiki/Port_numbers
+    """
+    __slots__ = tuple()
+    port_re = re.compile(r"^[0-9]*$")
+    _display_name = _('port')
+
+    def __init__(self, name, doc, default=None, default_multi=None,
+                 requires=None, multi=False, callback=None,
+                 callback_params=None, validator=None, validator_params=None,
+                 properties=None, allow_range=False, allow_zero=False,
+                 allow_wellknown=True, allow_registred=True,
+                 allow_private=False, warnings_only=False):
+        extra = {'_allow_range': allow_range,
+                 '_min_value': None,
+                 '_max_value': None}
+        ports_min = [0, 1, 1024, 49152]
+        ports_max = [0, 1023, 49151, 65535]
+        is_finally = False
+        for index, allowed in enumerate([allow_zero,
+                                         allow_wellknown,
+                                         allow_registred,
+                                         allow_private]):
+            if extra['_min_value'] is None:
+                if allowed:
+                    extra['_min_value'] = ports_min[index]
+            elif not allowed:
+                is_finally = True
+            elif allowed and is_finally:
+                raise ValueError(_('inconsistency in allowed range'))
+            if allowed:
+                extra['_max_value'] = ports_max[index]
+
+        if extra['_max_value'] is None:
+            raise ValueError(_('max value is empty'))
+
+        super(PortOption, self).__init__(name, doc, default=default,
+                                         default_multi=default_multi,
+                                         callback=callback,
+                                         callback_params=callback_params,
+                                         requires=requires,
+                                         multi=multi,
+                                         validator=validator,
+                                         validator_params=validator_params,
+                                         properties=properties,
+                                         warnings_only=warnings_only,
+                                         extra=extra)
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        if isinstance(value, int):
+            if sys.version_info[0] >= 3:  # pragma: no cover
+                value = str(value)
+            else:
+                value = unicode(value)
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+        if self._get_extra('_allow_range') and ":" in str(value):
+            value = str(value).split(':')
+            if len(value) != 2:
+                return ValueError(_('range must have two values only'))
+            if not value[0] < value[1]:
+                return ValueError(_('first port in range must be'
+                                    ' smaller than the second one'))
+        else:
+            value = [value]
+
+        for val in value:
+            if not self.port_re.search(val):
+                return ValueError()
+            val = int(val)
+            if not self._get_extra('_min_value') <= val <= self._get_extra('_max_value'):
+                return ValueError(_('must be an integer between {0} '
+                                    'and {1}').format(self._get_extra('_min_value'),
+                                                      self._get_extra('_max_value')))
diff --git a/tiramisu/option/stroption.py b/tiramisu/option/stroption.py
new file mode 100644 (file)
index 0000000..9a8c884
--- /dev/null
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import sys
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+
+
+class StrOption(Option):
+    "represents the choice of a string"
+    __slots__ = tuple()
+    _display_name = _('string')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        if not isinstance(value, str):
+            return ValueError()
+
+
+if sys.version_info[0] >= 3:  # pragma: no cover
+    #UnicodeOption is same as StrOption in python 3+
+    class UnicodeOption(StrOption):
+        __slots__ = tuple()
+        pass
+else:
+    class UnicodeOption(Option):
+        "represents the choice of a unicode string"
+        __slots__ = tuple()
+        _empty = u''
+        _display_name = _('unicode string')
+
+        def _validate(self, value, context=undefined, current_opt=undefined):
+            if not isinstance(value, unicode):
+                return ValueError()
diff --git a/tiramisu/option/urloption.py b/tiramisu/option/urloption.py
new file mode 100644 (file)
index 0000000..5574641
--- /dev/null
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import re
+
+from ..setting import undefined
+from ..i18n import _
+from .baseoption import Option
+from .domainnameoption import DomainnameOption
+
+
+class URLOption(DomainnameOption):
+    __slots__ = tuple()
+    proto_re = re.compile(r'(http|https)://')
+    path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
+    _display_name = _('URL')
+
+    def _validate(self, value, context=undefined, current_opt=undefined):
+        err = self._impl_valid_unicode(value)
+        if err:
+            return err
+        match = self.proto_re.search(value)
+        if not match:
+            return ValueError(_('must start with http:// or '
+                                'https://'))
+        value = value[len(match.group(0)):]
+        # get domain/files
+        splitted = value.split('/', 1)
+        if len(splitted) == 1:
+            domain = value
+            files = None
+        else:
+            domain, files = splitted
+        # if port in domain
+        splitted = domain.split(':', 1)
+        if len(splitted) == 1:
+            domain = splitted[0]
+            port = 0
+        else:
+            domain, port = splitted
+        if not 0 <= int(port) <= 65535:
+            return ValueError(_('port must be an between 0 and '
+                                '65536'))
+        # validate domainname
+        err = super(URLOption, self)._validate(domain)
+        if err:
+            return err
+        err = super(URLOption, self)._second_level_validation(domain, False)
+        if err:
+            return err
+        # validate file
+        if files is not None and files != '' and not self.path_re.search(files):
+            return ValueError(_('must ends with a valid resource name'))
+
+    def _second_level_validation(self, value, warnings_only):
+        pass
diff --git a/tiramisu/option/usernameoption.py b/tiramisu/option/usernameoption.py
new file mode 100644 (file)
index 0000000..bbf860f
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# 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/>.
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+import re
+
+from ..i18n import _
+from .option import _RegexpOption
+
+
+class UsernameOption(_RegexpOption):
+    __slots__ = tuple()
+    #regexp build with 'man 8 adduser' informations
+    _regexp = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
+    _display_name = _('username')