split tiramisu/option.py and add MasterSlaves object
authorEmmanuel Garette <egarette@cadoles.com>
Sat, 12 Apr 2014 09:53:58 +0000 (11:53 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sat, 12 Apr 2014 15:55:13 +0000 (17:55 +0200)
14 files changed:
ChangeLog [new file with mode: 0644]
test/test_option_calculation.py
test/test_option_consistency.py
test/test_requires.py
tiramisu/autolib.py
tiramisu/config.py
tiramisu/option.py [deleted file]
tiramisu/option/__init__.py [new file with mode: 0644]
tiramisu/option/baseoption.py [new file with mode: 0644]
tiramisu/option/masterslave.py [new file with mode: 0644]
tiramisu/option/option.py [new file with mode: 0644]
tiramisu/option/optiondescription.py [new file with mode: 0644]
tiramisu/setting.py
tiramisu/value.py

diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..23913bc
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,10 @@
+Sat Apr 12 11:37:27 CEST 2014  Emmanuel Garette <egarette@cadoles.com>
+
+       * behavior change in master/slave part of code:
+         if slave has a default value greater than master's one, it's raise
+         SlaveError, didn't try to reduce the slave's length
+       * tiramisu/option.py: split into tiramisu/option directory
+       * tiramisu/option/masterslave.py: master/slaves have no a special
+         object MasterSlaves for all code related to master/slaves options
+       * tiramisu/option/masterslave.py: master and slaves values (length,
+         consistency, ...) are now check every time
index de9c282..567f854 100644 (file)
@@ -1,8 +1,8 @@
 import autopath
 from py.test import raises
 
-from tiramisu.setting import groups
 from tiramisu.config import Config
+from tiramisu.setting import groups, owners
 from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
     StrOption, OptionDescription, SymLinkOption
 from tiramisu.error import PropertiesOptionError, ConflictError, SlaveError, ConfigError
@@ -430,6 +430,17 @@ def test_callback_multi_list_extend():
     assert cfg.val1 == ['1', '2', '3', '4', '5']
 
 
+def test_callback_multi_callback():
+    val1 = StrOption('val1', "", multi=True, callback=return_val)
+    interface1 = OptionDescription('val1', '', [val1])
+    maconfig = OptionDescription('rootconfig', '', [interface1])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    assert cfg.val1.val1 == ['val']
+    cfg.val1.val1.append()
+    assert cfg.val1.val1 == ['val', 'val']
+
+
 def test_callback_master_and_slaves_master():
     val1 = StrOption('val1', "", multi=True, callback=return_val)
     val2 = StrOption('val2', "", multi=True)
@@ -541,8 +552,6 @@ def test_callback_master_and_slaves_slave_cal():
     cfg.val3 = ['val1']
     assert cfg.val1.val1 == ['val1']
     assert cfg.val1.val2 == ['val']
-    assert cfg.val1.val1 == ['val1']
-    assert cfg.val1.val2 == ['val']
     del(cfg.val1.val1)
     cfg.val1.val2 = ['val']
     cfg.val3 = ['val1', 'val2']
@@ -571,8 +580,8 @@ def test_callback_master_and_slaves_slave_cal2():
     assert cfg.val1.val1 == ['val', 'val']
     assert cfg.val1.val2 == ['val2', 'val2']
     cfg.val3.pop(1)
-#    # cannot remove slave's value because master is calculated
-#    # so raise
+    # cannot remove slave's value because master is calculated
+    # so raise
     raises(SlaveError, "cfg.val1.val1")
     raises(SlaveError, "cfg.val1.val2")
     cfg.val3 = ['val', 'val']
@@ -585,6 +594,88 @@ def test_callback_master_and_slaves_slave_cal2():
     assert cfg.val1.val2 == ['val2', 'val2']
 
 
+def test_callback_master_and_slaves_master_disabled():
+    #properties must be transitive
+    val1 = StrOption('val1', "", multi=True, properties=('disabled',))
+    val2 = StrOption('val2', "", multi=True)
+    interface1 = OptionDescription('val1', '', [val1, val2])
+    interface1.impl_set_group_type(groups.master)
+    maconfig = OptionDescription('rootconfig', '', [interface1])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    raises(PropertiesOptionError, "cfg.val1.val1")
+    raises(PropertiesOptionError, "cfg.val1.val1.append('yes')")
+    raises(PropertiesOptionError, "cfg.val1.val2")
+
+
+def test_callback_master_and_slaves_master_callback_disabled():
+    val0 = StrOption('val0', "", multi=True, properties=('disabled',))
+    val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val0, False),)})
+    val2 = StrOption('val2', "", multi=True)
+    interface1 = OptionDescription('val1', '', [val1, val2])
+    interface1.impl_set_group_type(groups.master)
+    maconfig = OptionDescription('rootconfig', '', [interface1, val0])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    raises(ConfigError, "cfg.val1.val1")
+    raises(ConfigError, "cfg.val1.val2")
+    cfg.cfgimpl_get_settings().remove('disabled')
+    cfg.val1.val1 = []
+    cfg.cfgimpl_get_settings().append('disabled')
+    assert cfg.val1.val1 == []
+    assert cfg.val1.val2 == []
+
+
+def test_callback_master_and_slaves_slave_disabled():
+    val1 = StrOption('val1', "", multi=True)
+    val2 = StrOption('val2', "", multi=True, properties=('disabled',))
+    interface1 = OptionDescription('val1', '', [val1, val2])
+    interface1.impl_set_group_type(groups.master)
+    maconfig = OptionDescription('rootconfig', '', [interface1])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    assert cfg.val1.val1 == []
+    raises(PropertiesOptionError, "cfg.val1.val2")
+    cfg.val1.val1.append('yes')
+    assert cfg.val1.val1 == ['yes']
+    cfg.cfgimpl_get_settings().remove('disabled')
+    assert cfg.val1.val2 == [None]
+    cfg.val1.val2 = ['no']
+    cfg.val1.val1.append('yes2')
+    cfg.val1.val1.append('yes3')
+    cfg.val1.val2[2] = 'no1'
+    assert cfg.val1.val2 == ['no', None, 'no1']
+    cfg.cfgimpl_get_settings().append('disabled')
+    cfg.val1.val1.pop(0)
+    assert cfg.val1.val1 == ['yes2', 'yes3']
+    cfg.cfgimpl_get_settings().remove('disabled')
+    assert cfg.val1.val2 == [None, 'no1']
+
+
+def test_callback_master_and_slaves_slave_callback_disabled():
+    val0 = StrOption('val0', "", multi=True, properties=('disabled',))
+    val1 = StrOption('val1', "", multi=True)
+    val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val0, False),)})
+    interface1 = OptionDescription('val1', '', [val1, val2])
+    interface1.impl_set_group_type(groups.master)
+    maconfig = OptionDescription('rootconfig', '', [interface1, val0])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    assert cfg.val1.val1 == []
+    raises(ConfigError, "cfg.val1.val2")
+    cfg.val1.val1.append('yes')
+    assert cfg.val1.val1 == ['yes']
+    cfg.cfgimpl_get_settings().remove('disabled')
+    assert cfg.val1.val2 == [None]
+    cfg.val1.val2 = ['no']
+    cfg.val1.val1.append('yes1')
+    cfg.val1.val2[1] = 'no1'
+    cfg.cfgimpl_get_settings().append('disabled')
+    cfg.val1.val1.pop(0)
+    assert cfg.val1.val1 == ['yes1']
+    assert cfg.val1.val2 == ['no1']
+
+
 def test_callback_master_and_slaves_slave_list():
     val1 = StrOption('val1', "", multi=True)
     val2 = StrOption('val2', "", multi=True, callback=return_list)
@@ -593,20 +684,20 @@ def test_callback_master_and_slaves_slave_list():
     maconfig = OptionDescription('rootconfig', '', [interface1])
     cfg = Config(maconfig)
     cfg.read_write()
-    assert cfg.val1.val2 == []
+    #len is equal to 2 for slave and 0 for master
+    raises(SlaveError, "cfg.val1.val2")
     cfg.val1.val1 = ['val1', 'val2']
     assert cfg.val1.val1 == ['val1', 'val2']
     assert cfg.val1.val2 == ['val', 'val']
-    cfg.val1.val1 = ['val1']
     #wrong len
-    raises(SlaveError, 'cfg.val1.val2')
+    raises(SlaveError, "cfg.val1.val1 = ['val1']")
 
 
 def test_callback_master_and_slaves_value():
+    val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
     val1 = StrOption('val1', "", multi=True)
     val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
     val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)})
-    val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
     val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)})
     val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val5, False),)})
     interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6])
@@ -614,27 +705,24 @@ def test_callback_master_and_slaves_value():
     maconfig = OptionDescription('rootconfig', '', [interface1, val4])
     cfg = Config(maconfig)
     cfg.read_write()
-    assert cfg.val1.val1 == []
-    assert cfg.val1.val2 == []
-    assert cfg.val1.val3 == []
-    assert cfg.val1.val5 == []
-    assert cfg.val1.val6 == []
+    cfg.val4 == ['val10', 'val11']
+    raises(SlaveError, "cfg.val1.val1")
+    raises(SlaveError, "cfg.val1.val2")
+    raises(SlaveError, "cfg.val1.val3")
+    raises(SlaveError, "cfg.val1.val5")
+    raises(SlaveError, "cfg.val1.val6")
     #
-    cfg.val1.val1 = ['val1']
-    assert cfg.val1.val1 == ['val1']
-    assert cfg.val1.val2 == ['val1']
-    assert cfg.val1.val3 == ['yes']
-    assert cfg.val1.val5 == ['val10']
-    assert cfg.val1.val6 == ['val10']
+    #default calculation has greater length
+    raises(SlaveError, "cfg.val1.val1 = ['val1']")
     #
-    cfg.val1.val1.append('val2')
+    cfg.val1.val1 = ['val1', 'val2']
     assert cfg.val1.val1 == ['val1', 'val2']
     assert cfg.val1.val2 == ['val1', 'val2']
     assert cfg.val1.val3 == ['yes', 'yes']
     assert cfg.val1.val5 == ['val10', 'val11']
     assert cfg.val1.val6 == ['val10', 'val11']
     #
-    cfg.val1.val1 = ['val1', 'val2', 'val3']
+    cfg.val1.val1.append('val3')
     assert cfg.val1.val1 == ['val1', 'val2', 'val3']
     assert cfg.val1.val2 == ['val1', 'val2', 'val3']
     assert cfg.val1.val3 == ['yes', 'yes', 'yes']
@@ -705,9 +793,16 @@ def test_callback_master_and_other_master_slave():
     assert cfg.val4.val6 == ['no', None]
     cfg.val1.val1 = ['yes', 'yes', 'yes']
     cfg.val1.val2 = ['no', 'no', 'no']
-    assert cfg.val4.val4 == ['val10', 'val11']
-    assert cfg.val4.val5 == ['yes', 'yes']
-    assert cfg.val4.val6 == ['no', 'no']
+    raises(SlaveError, "cfg.val4.val4")
+    raises(SlaveError, "cfg.val4.val5")
+    raises(SlaveError, "cfg.val4.val6")
+    cfg.val4.getattr('val4', validate=False).append('val12')
+    assert cfg.val4.val4 == ['val10', 'val11', 'val12']
+    assert cfg.val4.val5 == ['yes', 'yes', 'yes']
+    assert cfg.val4.val6 == ['no', 'no', 'no']
+
+
+#FIXME: slave est un symlink
 
 
 def test_callback_different_type():
@@ -757,6 +852,19 @@ def test_callback_two_disabled():
     raises(PropertiesOptionError, 'cfg.od2.opt2')
 
 
+def test_callback_two_disabled2():
+    opt1 = BoolOption('opt1', '', properties=('hidden',))
+    opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('hidden',))
+    od1 = OptionDescription('od1', '', [opt1])
+    od2 = OptionDescription('od2', '', [opt2])
+    maconfig = OptionDescription('rootconfig', '', [od1, od2])
+    cfg = Config(maconfig)
+    cfg.read_write()
+    cfg.cfgimpl_get_settings().setpermissive(('hidden',))
+    raises(PropertiesOptionError, 'cfg.od2.opt2')
+    assert cfg.getowner(opt2, force_permissive=True) == owners.default
+
+
 def test_callback_calculating_disabled():
     opt1 = BoolOption('opt1', '', properties=('disabled',))
     opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)})
@@ -779,6 +887,17 @@ def test_callback_calculating_mandatory():
     raises(ConfigError, 'cfg.od2.opt2')
 
 
+def test_callback_calculating_mandatory_multi():
+    opt1 = BoolOption('opt1', '', multi=True, properties=('disabled',))
+    opt2 = BoolOption('opt2', '', multi=True, callback=return_value, callback_params={'': ((opt1, False),)}, properties=('mandatory',))
+    od1 = OptionDescription('od1', '', [opt1])
+    od2 = OptionDescription('od2', '', [opt2])
+    maconfig = OptionDescription('rootconfig', '', [od1, od2])
+    cfg = Config(maconfig)
+    cfg.read_only()
+    raises(ConfigError, 'cfg.od2.opt2')
+
+
 def test_callback_two_disabled_multi():
     opt1 = BoolOption('opt1', '', properties=('disabled',))
     opt2 = BoolOption('opt2', '', callback=return_value, callback_params={'': ((opt1, False),)}, properties=('disabled',), multi=True)
index edb4f4a..5d2f9da 100644 (file)
@@ -321,7 +321,7 @@ def test_consistency_broadcast_error():
     c.impl_add_consistency('broadcast', a)
     c = Config(od)
     c.a = ['192.168.1.0']
-    c.b = ['255.255.255.0']
+    raises(ConfigError, "c.b = ['255.255.255.0']")
     raises(ConfigError, "c.c = ['192.168.1.255']")
 
 
index ff9c2a6..c6d1032 100644 (file)
@@ -242,6 +242,30 @@ def test_requires_transitive():
     assert props == ['disabled']
 
 
+def test_requires_transitive_owner():
+    a = BoolOption('activate_service', '', True)
+    b = BoolOption('activate_service_web', '', True,
+                   requires=[{'option': a, 'expected': False, 'action': 'disabled'}])
+
+    d = IPOption('ip_address_service_web', '',
+                 requires=[{'option': b, 'expected': False, 'action': 'disabled'}])
+    od = OptionDescription('service', '', [a, b, d])
+    c = Config(od)
+    c.read_write()
+    c.activate_service
+    c.activate_service_web
+    c.ip_address_service_web
+    #no more default value
+    c.ip_address_service_web = '1.1.1.1'
+    c.activate_service = False
+    props = []
+    try:
+        c.ip_address_service_web
+    except PropertiesOptionError as err:
+        props = err.proptype
+    assert props == ['disabled']
+
+
 def test_requires_transitive_bis():
     a = BoolOption('activate_service', '', True)
     abis = BoolOption('activate_service_bis', '', True)
index 92b86c5..6d8ed6e 100644 (file)
 # ____________________________________________________________
 "enables us to carry out a calculation and return an option's value"
 from tiramisu.error import PropertiesOptionError, ConfigError
-from tiramisu.setting import multitypes
 from tiramisu.i18n import _
+from tiramisu.setting import undefined
 # ____________________________________________________________
 
 
 def carry_out_calculation(option, config, callback, callback_params,
-                          index=None, max_len=None):
+                          index=undefined):
     """a function that carries out a calculation for an option's value
 
     :param name: the option
@@ -38,8 +38,6 @@ def carry_out_calculation(option, config, callback, callback_params,
     :type callback_params: dict
     :param index: if an option is multi, only calculates the nth value
     :type index: int
-    :param max_len: max length for a multi
-    :type max_len: int
 
     The callback_params is a dict. Key is used to build args (if key is '')
     and kwargs (otherwise). Values are tuple of:
@@ -129,7 +127,7 @@ def carry_out_calculation(option, config, callback, callback_params,
     * if callback_params={'value': ((opt1, False), (opt2, False))}
       => raises ValueError()
 
-    If index is not None, return a value, otherwise return:
+    If index is not undefined, return a value, otherwise return:
 
     * a list if one parameters have multi option
     * a value otherwise
@@ -154,15 +152,8 @@ def carry_out_calculation(option, config, callback, callback_params,
                     ).impl_get_path_by_opt(opt)
                     # get value
                     try:
-                        if option.impl_is_multi() and \
-                                (option.impl_get_multitype() == multitypes.master or
-                                 (option.impl_get_multitype() == multitypes.slave and
-                                  option.impl_get_master_slaves() != opt)):
-                            validate = True
-                        else:
-                            validate = False
                         value = config.getattr(path, force_permissive=True,
-                                               validate=validate)
+                                               validate=False)
                         # convert to list, not modifie this multi
                         if value.__class__.__name__ == 'Multi':
                             value = list(value)
@@ -175,20 +166,13 @@ def carry_out_calculation(option, config, callback, callback_params,
                                                                err.proptype,
                                                                option._name))
 
-                    is_multi = False
-                    if opt.impl_is_multi():
-                        #opt is master, search if option is a slave
-                        if opt.impl_get_multitype() == multitypes.master:
-                            if option in opt.impl_get_master_slaves():
-                                is_multi = True
-                        #opt is slave, search if option is an other slaves
-                        elif opt.impl_get_multitype() == multitypes.slave:
-                            if option in opt.impl_get_master_slaves(
-                            ).impl_get_master_slaves():
-                                is_multi = True
-                    if is_multi:
+                    if opt.impl_is_master_slaves() and \
+                            opt.impl_get_master_slaves().in_same_group(option):
                         len_multi = len(value)
                         one_is_multi = True
+                        is_multi = True
+                    else:
+                        is_multi = False
                     tcparams.setdefault(key, []).append((value, is_multi))
             else:
                 # callbk is a value and not a multi
@@ -199,7 +183,7 @@ def carry_out_calculation(option, config, callback, callback_params,
     # if no index, return a list
     if one_is_multi:
         ret = []
-        if index is not None:
+        if index is not undefined:
             range_ = [index]
         else:
             range_ = range(len_multi)
@@ -218,7 +202,7 @@ def carry_out_calculation(option, config, callback, callback_params,
                     else:
                         kwargs[key] = val
             calc = calculate(callback, args, kwargs)
-            if index is not None:
+            if index is not undefined:
                 ret = calc
             else:
                 ret.append(calc)
@@ -237,11 +221,7 @@ def carry_out_calculation(option, config, callback, callback_params,
                     kwargs[key] = couple[0]
         ret = calculate(callback, args, kwargs)
         if callback_params != {}:
-            if isinstance(ret, list) and max_len:
-                ret = ret[:max_len]
-                if len(ret) < max_len:
-                    ret = ret + [None] * (max_len - len(ret))
-            if isinstance(ret, list) and index is not None:
+            if isinstance(ret, list) and index is not undefined:
                 if len(ret) < index + 1:
                     ret = None
                 else:
index 8356aaa..04c9cb9 100644 (file)
@@ -228,6 +228,7 @@ class SubConfig(object):
         :return: option's value if name is an option name, OptionDescription
                  otherwise
         """
+        #FIXME force_properties vraiment utile maintenant ?
         # attribute access by passing a path,
         # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
         if '.' in name:
@@ -243,6 +244,8 @@ class SubConfig(object):
         else:
             subpath = self._impl_path + '.' + name
         # symlink options
+        #FIXME a gerer plutot dans l'option ca ...
+        #FIXME je n'en sais rien en fait ... :/
         if isinstance(opt_or_descr, SymLinkOption):
             context = self._cfgimpl_get_context()
             path = context.cfgimpl_get_description().impl_get_path_by_opt(
@@ -257,7 +260,7 @@ class SubConfig(object):
                 force_properties=force_properties)
             return SubConfig(opt_or_descr, self._impl_context, subpath)
         else:
-            return self.cfgimpl_get_values().getitem(
+            return self.cfgimpl_get_values()._get_cached_item(
                 opt_or_descr, path=subpath,
                 validate=validate,
                 force_properties=force_properties,
diff --git a/tiramisu/option.py b/tiramisu/option.py
deleted file mode 100644 (file)
index 820908e..0000000
+++ /dev/null
@@ -1,1564 +0,0 @@
-# -*- coding: utf-8 -*-
-"option types and option description"
-# Copyright (C) 2012-2013 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 copy import copy, deepcopy
-from types import FunctionType
-from IPy import IP
-import warnings
-
-from tiramisu.error import ConfigError, ConflictError, ValueWarning
-from tiramisu.setting import groups, multitypes, log
-from tiramisu.i18n import _
-from tiramisu.autolib import carry_out_calculation
-
-name_regexp = re.compile(r'^\d+')
-forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
-                   'make_dict', 'unwrap_from_path', 'read_only',
-                   'read_write', 'getowner', 'set_contexts')
-
-
-def valid_name(name):
-    "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
-    if not isinstance(name, str):
-        return False
-    if re.match(name_regexp, name) is None and not name.startswith('_') \
-            and name not in forbidden_names \
-            and not name.startswith('impl_') \
-            and not name.startswith('cfgimpl_'):
-        return True
-    else:
-        return False
-#____________________________________________________________
-#
-
-
-class BaseOption(object):
-    """This abstract base class stands for attribute access
-    in options that have to be set only once, it is of course done in the
-    __setattr__ method
-    """
-    __slots__ = ('_name', '_requires', '_properties', '_readonly',
-                 '_calc_properties', '_impl_informations',
-                 '_state_readonly', '_state_requires', '_stated')
-
-    def __init__(self, name, doc, requires, properties):
-        if not valid_name(name):
-            raise ValueError(_("invalid name: {0} for option").format(name))
-        self._name = name
-        self._impl_informations = {}
-        self.impl_set_information('doc', doc)
-        self._calc_properties, self._requires = validate_requires_arg(
-            requires, self._name)
-        if properties is None:
-            properties = tuple()
-        if not isinstance(properties, tuple):
-            raise TypeError(_('invalid properties type {0} for {1},'
-                            ' must be a tuple').format(
-                                type(properties),
-                                self._name))
-        if self._calc_properties is not None and properties is not tuple():
-            set_forbidden_properties = set(properties) & self._calc_properties
-            if set_forbidden_properties != frozenset():
-                raise ValueError('conflict: properties already set in '
-                                 'requirement {0}'.format(
-                                     list(set_forbidden_properties)))
-        self._properties = properties  # 'hidden', 'disabled'...
-
-    def __setattr__(self, name, value):
-        """set once and only once some attributes in the option,
-        like `_name`. `_name` cannot be changed one the option and
-        pushed in the :class:`tiramisu.option.OptionDescription`.
-
-        if the attribute `_readonly` is set to `True`, the option is
-        "frozen" (which has noting to do with the high level "freeze"
-        propertie or "read_only" property)
-        """
-        if not name.startswith('_state') and not name.startswith('_cache'):
-            is_readonly = False
-            # never change _name
-            if name == '_name':
-                try:
-                    self._name
-                    #so _name is already set
-                    is_readonly = True
-                except:
-                    pass
-            elif name != '_readonly':
-                try:
-                    if self._readonly is True:
-                        is_readonly = True
-                except AttributeError:
-                    self._readonly = False
-            if is_readonly:
-                raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
-                                       " read-only").format(
-                                           self.__class__.__name__,
-                                           self._name,
-                                           name))
-        object.__setattr__(self, name, value)
-
-    # information
-    def impl_set_information(self, key, value):
-        """updates the information's attribute
-        (which is a dictionary)
-
-        :param key: information's key (ex: "help", "doc"
-        :param value: information's value (ex: "the help string")
-        """
-        self._impl_informations[key] = value
-
-    def impl_get_information(self, key, default=None):
-        """retrieves one information's item
-
-        :param key: the item string (ex: "help")
-        """
-        if key in self._impl_informations:
-            return self._impl_informations[key]
-        elif default is not None:
-            return default
-        else:
-            raise ValueError(_("information's item not found: {0}").format(
-                key))
-
-    def _impl_convert_requires(self, descr, load=False):
-        """export of the requires during the serialization process
-
-        :type descr: :class:`tiramisu.option.OptionDescription`
-        :param load: `True` if we are at the init of the option description
-        :type load: bool
-        """
-        if not load and self._requires is None:
-            self._state_requires = None
-        elif load and self._state_requires is None:
-            self._requires = None
-            del(self._state_requires)
-        else:
-            if load:
-                _requires = self._state_requires
-            else:
-                _requires = self._requires
-            new_value = []
-            for requires in _requires:
-                new_requires = []
-                for require in requires:
-                    if load:
-                        new_require = [descr.impl_get_opt_by_path(require[0])]
-                    else:
-                        new_require = [descr.impl_get_path_by_opt(require[0])]
-                    new_require.extend(require[1:])
-                    new_requires.append(tuple(new_require))
-                new_value.append(tuple(new_requires))
-            if load:
-                del(self._state_requires)
-                self._requires = new_value
-            else:
-                self._state_requires = new_value
-
-    # serialize
-    def _impl_getstate(self, descr):
-        """the under the hood stuff that need to be done
-        before the serialization.
-
-        :param descr: the parent :class:`tiramisu.option.OptionDescription`
-        """
-        self._stated = True
-        for func in dir(self):
-            if func.startswith('_impl_convert_'):
-                getattr(self, func)(descr)
-        self._state_readonly = self._readonly
-
-    def __getstate__(self, stated=True):
-        """special method to enable the serialization with pickle
-        Usualy, a `__getstate__` method does'nt need any parameter,
-        but somme under the hood stuff need to be done before this action
-
-        :parameter stated: if stated is `True`, the serialization protocol
-                           can be performed, not ready yet otherwise
-        :parameter type: bool
-        """
-        try:
-            self._stated
-        except AttributeError:
-            raise SystemError(_('cannot serialize Option, '
-                                'only in OptionDescription'))
-        slots = set()
-        for subclass in self.__class__.__mro__:
-            if subclass is not object:
-                slots.update(subclass.__slots__)
-        slots -= frozenset(['_cache_paths', '_cache_consistencies',
-                            '__weakref__'])
-        states = {}
-        for slot in slots:
-            # remove variable if save variable converted
-            # in _state_xxxx variable
-            if '_state' + slot not in slots:
-                if slot.startswith('_state'):
-                    # should exists
-                    states[slot] = getattr(self, slot)
-                    # remove _state_xxx variable
-                    self.__delattr__(slot)
-                else:
-                    try:
-                        states[slot] = getattr(self, slot)
-                    except AttributeError:
-                        pass
-        if not stated:
-            del(states['_stated'])
-        return states
-
-    # unserialize
-    def _impl_setstate(self, descr):
-        """the under the hood stuff that need to be done
-        before the serialization.
-
-        :type descr: :class:`tiramisu.option.OptionDescription`
-        """
-        for func in dir(self):
-            if func.startswith('_impl_convert_'):
-                getattr(self, func)(descr, load=True)
-        try:
-            self._readonly = self._state_readonly
-            del(self._state_readonly)
-            del(self._stated)
-        except AttributeError:
-            pass
-
-    def __setstate__(self, state):
-        """special method that enables us to serialize (pickle)
-
-        Usualy, a `__setstate__` method does'nt need any parameter,
-        but somme under the hood stuff need to be done before this action
-
-        :parameter state: a dict is passed to the loads, it is the attributes
-                          of the options object
-        :type state: dict
-        """
-        for key, value in state.items():
-            setattr(self, key, value)
-
-
-class Option(BaseOption):
-    """
-    Abstract base class for configuration option's.
-
-    Reminder: an Option object is **not** a container for the value.
-    """
-    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
-                 '_state_callback', '_callback', '_multitype',
-                 '_consistencies', '_warnings_only', '_master_slaves',
-                 '_state_consistencies', '__weakref__')
-    _empty = ''
-
-    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, warnings_only=False):
-        """
-        :param name: the option's name
-        :param doc: the option's description
-        :param default: specifies the default value of the option,
-                        for a multi : ['bla', 'bla', 'bla']
-        :param default_multi: 'bla' (used in case of a reset to default only at
-                        a given index)
-        :param requires: is a list of names of options located anywhere
-                         in the configuration.
-        :param multi: if true, the option's value is a list
-        :param callback: the name of a function. If set, the function's output
-                         is responsible of the option's value
-        :param callback_params: the callback's parameter
-        :param validator: the name of a function which stands for a custom
-                          validation of the value
-        :param validator_params: the validator's parameters
-        :param properties: tuple of default properties
-        :param warnings_only: _validator and _consistencies don't raise if True
-                             Values()._warning contain message
-
-        """
-        super(Option, self).__init__(name, doc, requires, properties)
-        self._multi = multi
-        if validator is not None:
-            validate_callback(validator, validator_params, 'validator')
-            self._validator = (validator, validator_params)
-        else:
-            self._validator = None
-        if not self._multi and default_multi is not None:
-            raise ValueError(_("a default_multi is set whereas multi is False"
-                             " in option: {0}").format(name))
-        if default_multi is not None:
-            try:
-                self._validate(default_multi)
-            except ValueError as err:
-                raise ValueError(_("invalid default_multi value {0} "
-                                   "for option {1}: {2}").format(
-                                       str(default_multi), name, err))
-        if callback is not None and (default is not None or
-                                     default_multi is not None):
-            raise ValueError(_("default value not allowed if option: {0} "
-                             "is calculated").format(name))
-        if callback is None and callback_params is not None:
-            raise ValueError(_("params defined for a callback function but "
-                             "no callback defined"
-                             " yet for option {0}").format(name))
-        if callback is not None:
-            validate_callback(callback, callback_params, 'callback')
-            self._callback = (callback, callback_params)
-        else:
-            self._callback = None
-        if self._multi:
-            if default is None:
-                default = []
-            self._multitype = multitypes.default
-            self._default_multi = default_multi
-        self._warnings_only = warnings_only
-        self.impl_validate(default)
-        self._default = default
-        self._consistencies = None
-
-    def _launch_consistency(self, func, option, value, context, index,
-                            all_cons_opts, warnings_only):
-        """Launch consistency now
-
-        :param func: function name, this name should start with _cons_
-        :type func: `str`
-        :param option: option that value is changing
-        :type option: `tiramisu.option.Option`
-        :param value: new value of this option
-        :param context: Config's context, if None, check default value instead
-        :type context: `tiramisu.config.Config`
-        :param index: only for multi option, consistency should be launch for
-                      specified index
-        :type index: `int`
-        :param all_cons_opts: all options concerne by this consistency
-        :type all_cons_opts: `list` of `tiramisu.option.Option`
-        :param warnings_only: specific raise error for warning
-        :type warnings_only: `boolean`
-        """
-        if context is not None:
-            descr = context.cfgimpl_get_description()
-
-        all_cons_vals = []
-        for opt in all_cons_opts:
-            #get value
-            if option == opt:
-                opt_value = value
-            else:
-                #if context, calculate value, otherwise get default value
-                if context is not None:
-                    opt_value = context.getattr(
-                        descr.impl_get_path_by_opt(opt), validate=False,
-                        force_permissive=True)
-                else:
-                    opt_value = opt.impl_getdefault()
-
-            #append value
-            if not self.impl_is_multi() or option == opt:
-                all_cons_vals.append(opt_value)
-            else:
-                #value is not already set, could be higher index
-                try:
-                    all_cons_vals.append(opt_value[index])
-                except IndexError:
-                    #so return if no value
-                    return
-        getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
-
-    def impl_validate(self, value, context=None, validate=True,
-                      force_index=None):
-        """
-        :param value: the option's value
-        :param context: Config's context
-        :type context: :class:`tiramisu.config.Config`
-        :param validate: if true enables ``self._validator`` validation
-        :type validate: boolean
-        :param force_no_multi: if multi, value has to be a list
-                               not if force_no_multi is True
-        :type force_no_multi: boolean
-        """
-        if not validate:
-            return
-
-        def val_validator(val):
-            if self._validator is not None:
-                if self._validator[1] is not None:
-                    validator_params = deepcopy(self._validator[1])
-                    if '' in validator_params:
-                        lst = list(validator_params[''])
-                        lst.insert(0, val)
-                        validator_params[''] = tuple(lst)
-                    else:
-                        validator_params[''] = (val,)
-                else:
-                    validator_params = {'': (val,)}
-                # Raise ValueError if not valid
-                carry_out_calculation(self, config=context,
-                                      callback=self._validator[0],
-                                      callback_params=validator_params)
-
-        def do_validation(_value, _index=None):
-            if _value is None:
-                return
-            # option validation
-            try:
-                self._validate(_value)
-            except ValueError as err:
-                raise ValueError(_('invalid value for option {0}: {1}'
-                                   '').format(self._name, err))
-            error = None
-            warning = None
-            try:
-                # valid with self._validator
-                val_validator(_value)
-                self._second_level_validation(_value, self._warnings_only)
-            except ValueError as error:
-                log.debug(_('do_validation for {0}: error in value').format(
-                    self._name), exc_info=True)
-                if self._warnings_only:
-                    warning = error
-                    error = None
-            except ValueWarning as warning:
-                log.debug(_('do_validation for {0}: warning in value').format(
-                    self._name), exc_info=True)
-                pass
-            if error is None and warning is None:
-                try:
-                    # if context launch consistency validation
-                    if context is not None:
-                        descr._valid_consistency(self, _value, context, _index)
-                except ValueError as error:
-                    log.debug(_('do_validation for {0}: error in consistency').format(
-                        self._name), exc_info=True)
-                    pass
-                except ValueWarning as warning:
-                    log.debug(_('do_validation for {0}: warning in consistency').format(
-                        self._name), exc_info=True)
-                    pass
-            if warning:
-                msg = _("warning on the value of the option {0}: {1}").format(
-                    self._name, warning)
-                warnings.warn_explicit(ValueWarning(msg, self),
-                                       ValueWarning,
-                                       self.__class__.__name__, 0)
-            elif error:
-                raise ValueError(_("invalid value for option {0}: {1}").format(
-                    self._name, error))
-
-        # generic calculation
-        if context is not None:
-            descr = context.cfgimpl_get_description()
-
-        if not self._multi or force_index is not None:
-            do_validation(value, force_index)
-        else:
-            if not isinstance(value, list):
-                raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self._name))
-            for index, val in enumerate(value):
-                do_validation(val, index)
-
-    def impl_getdefault(self):
-        "accessing the default value"
-        return self._default
-
-    def impl_getdefault_multi(self):
-        "accessing the default value for a multi"
-        return self._default_multi
-
-    def impl_get_multitype(self):
-        return self._multitype
-
-    def impl_get_master_slaves(self):
-        return self._master_slaves
-
-    def impl_is_empty_by_default(self):
-        "no default value has been set yet"
-        if ((not self.impl_is_multi() and self._default is None) or
-                (self.impl_is_multi() and (self._default == []
-                                           or None in self._default))):
-            return True
-        return False
-
-    def impl_getdoc(self):
-        "accesses the Option's doc"
-        return self.impl_get_information('doc')
-
-    def impl_has_callback(self):
-        "to know if a callback has been defined or not"
-        if self._callback is None:
-            return False
-        else:
-            return True
-
-    def impl_getkey(self, value):
-        return value
-
-    def impl_is_multi(self):
-        return self._multi
-
-    def impl_add_consistency(self, func, *other_opts, **params):
-        """Add consistency means that value will be validate with other_opts
-        option's values.
-
-        :param func: function's name
-        :type func: `str`
-        :param other_opts: options used to validate value
-        :type other_opts: `list` of `tiramisu.option.Option`
-        :param params: extra params (only warnings_only are allowed)
-        """
-        if self._consistencies is None:
-            self._consistencies = []
-        warnings_only = params.get('warnings_only', False)
-        for opt in other_opts:
-            if not isinstance(opt, Option):
-                raise ConfigError(_('consistency must be set with an option'))
-            if self is opt:
-                raise ConfigError(_('cannot add consistency with itself'))
-            if self.impl_is_multi() != opt.impl_is_multi():
-                raise ConfigError(_('every options in consistency must be '
-                                    'multi or none'))
-        func = '_cons_{0}'.format(func)
-        all_cons_opts = tuple([self] + list(other_opts))
-        value = self.impl_getdefault()
-        if value is not None:
-            if self.impl_is_multi():
-                for idx, val in enumerate(value):
-                    self._launch_consistency(func, self, val, None,
-                                             idx, all_cons_opts, warnings_only)
-            else:
-                self._launch_consistency(func, self, value, None,
-                                         None, all_cons_opts, warnings_only)
-        self._consistencies.append((func, all_cons_opts, params))
-        self.impl_validate(self.impl_getdefault())
-
-    def _cons_not_equal(self, opts, vals, warnings_only):
-        for idx_inf, val_inf in enumerate(vals):
-            for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
-                if val_inf == val_sup is not None:
-                    if warnings_only:
-                        msg = _("same value for {0} and {1}, should be different")
-                    else:
-                        msg = _("same value for {0} and {1}, must be different")
-                    raise ValueError(msg.format(opts[idx_inf]._name,
-                                                opts[idx_inf + idx_sup + 1]._name))
-
-    def _impl_convert_callbacks(self, descr, load=False):
-        if not load and self._callback is None:
-            self._state_callback = None
-        elif load and self._state_callback is None:
-            self._callback = None
-            del(self._state_callback)
-        else:
-            if load:
-                callback, callback_params = self._state_callback
-            else:
-                callback, callback_params = self._callback
-            if callback_params is not None:
-                cllbck_prms = {}
-                for key, values in callback_params.items():
-                    vls = []
-                    for value in values:
-                        if isinstance(value, tuple):
-                            if load:
-                                value = (descr.impl_get_opt_by_path(value[0]),
-                                         value[1])
-                            else:
-                                value = (descr.impl_get_path_by_opt(value[0]),
-                                         value[1])
-                        vls.append(value)
-                    cllbck_prms[key] = tuple(vls)
-            else:
-                cllbck_prms = None
-
-            if load:
-                del(self._state_callback)
-                self._callback = (callback, cllbck_prms)
-            else:
-                self._state_callback = (callback, cllbck_prms)
-
-    # serialize/unserialize
-    def _impl_convert_consistencies(self, descr, load=False):
-        """during serialization process, many things have to be done.
-        one of them is the localisation of the options.
-        The paths are set once for all.
-
-        :type descr: :class:`tiramisu.option.OptionDescription`
-        :param load: `True` if we are at the init of the option description
-        :type load: bool
-        """
-        if not load and self._consistencies is None:
-            self._state_consistencies = None
-        elif load and self._state_consistencies is None:
-            self._consistencies = None
-            del(self._state_consistencies)
-        else:
-            if load:
-                consistencies = self._state_consistencies
-            else:
-                consistencies = self._consistencies
-            new_value = []
-            for consistency in consistencies:
-                values = []
-                for obj in consistency[1]:
-                    if load:
-                        values.append(descr.impl_get_opt_by_path(obj))
-                    else:
-                        values.append(descr.impl_get_path_by_opt(obj))
-                new_value.append((consistency[0], tuple(values), consistency[2]))
-            if load:
-                del(self._state_consistencies)
-                self._consistencies = new_value
-            else:
-                self._state_consistencies = new_value
-
-    def _second_level_validation(self, value, warnings_only):
-        pass
-
-
-class ChoiceOption(Option):
-    """represents a choice out of several objects.
-
-    The option can also have the value ``None``
-    """
-
-    __slots__ = ('_values', '_open_values')
-    _opt_type = 'string'
-
-    def __init__(self, name, doc, values, default=None, default_multi=None,
-                 requires=None, multi=False, callback=None,
-                 callback_params=None, open_values=False, validator=None,
-                 validator_params=None, properties=None, warnings_only=False):
-        """
-        :param values: is a list of values the option can possibly take
-        """
-        if not isinstance(values, tuple):
-            raise TypeError(_('values must be a tuple for {0}').format(name))
-        self._values = values
-        if open_values not in (True, False):
-            raise TypeError(_('open_values must be a boolean for '
-                            '{0}').format(name))
-        self._open_values = open_values
-        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):
-        return self._values
-
-    def impl_is_openvalues(self):
-        return self._open_values
-
-    def _validate(self, value):
-        if not self.impl_is_openvalues() and not value in self.impl_get_values():
-            raise ValueError(_('value {0} is not permitted, '
-                               'only {1} is allowed'
-                               '').format(value, self._values))
-
-
-class BoolOption(Option):
-    "represents a choice between ``True`` and ``False``"
-    __slots__ = tuple()
-    _opt_type = 'bool'
-
-    def _validate(self, value):
-        if not isinstance(value, bool):
-            raise ValueError(_('invalid boolean'))
-
-
-class IntOption(Option):
-    "represents a choice of an integer"
-    __slots__ = tuple()
-    _opt_type = 'int'
-
-    def _validate(self, value):
-        if not isinstance(value, int):
-            raise ValueError(_('invalid integer'))
-
-
-class FloatOption(Option):
-    "represents a choice of a floating point number"
-    __slots__ = tuple()
-    _opt_type = 'float'
-
-    def _validate(self, value):
-        if not isinstance(value, float):
-            raise ValueError(_('invalid float'))
-
-
-class StrOption(Option):
-    "represents the choice of a string"
-    __slots__ = tuple()
-    _opt_type = 'string'
-
-    def _validate(self, value):
-        if not isinstance(value, str):
-            raise ValueError(_('invalid string'))
-
-
-if sys.version_info[0] >= 3:
-    #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()
-        _opt_type = 'unicode'
-        _empty = u''
-
-        def _validate(self, value):
-            if not isinstance(value, unicode):
-                raise ValueError(_('invalid unicode'))
-
-
-class SymLinkOption(BaseOption):
-    __slots__ = ('_name', '_opt', '_state_opt')
-    _opt_type = 'symlink'
-    #not return _opt consistencies
-    _consistencies = None
-
-    def __init__(self, name, opt):
-        self._name = name
-        if not isinstance(opt, Option):
-            raise ValueError(_('malformed symlinkoption '
-                               'must be an option '
-                               'for symlink {0}').format(name))
-        self._opt = opt
-        self._readonly = True
-
-    def __getattr__(self, name):
-        if name in ('_name', '_opt', '_opt_type', '_readonly'):
-            return object.__getattr__(self, name)
-        else:
-            return getattr(self._opt, name)
-
-    def _impl_getstate(self, descr):
-        super(SymLinkOption, self)._impl_getstate(descr)
-        self._state_opt = descr.impl_get_path_by_opt(self._opt)
-
-    def _impl_setstate(self, descr):
-        self._opt = descr.impl_get_opt_by_path(self._state_opt)
-        del(self._state_opt)
-        super(SymLinkOption, self)._impl_setstate(descr)
-
-
-class IPOption(Option):
-    "represents the choice of an ip"
-    __slots__ = ('_private_only', '_allow_reserved')
-    _opt_type = '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):
-        self._private_only = private_only
-        self._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)
-
-    def _validate(self, value):
-        # sometimes an ip term starts with a zero
-        # but this does not fit in some case, for example bind does not like it
-        try:
-            for val in value.split('.'):
-                if val.startswith("0") and len(val) > 1:
-                    raise ValueError(_('invalid IP'))
-        except AttributeError:
-            #if integer for example
-            raise ValueError(_('invalid IP'))
-        # 'standard' validation
-        try:
-            IP('{0}/32'.format(value))
-        except ValueError:
-            raise ValueError(_('invalid IP'))
-
-    def _second_level_validation(self, value, warnings_only):
-        ip = IP('{0}/32'.format(value))
-        if not self._allow_reserved and ip.iptype() == 'RESERVED':
-            if warnings_only:
-                msg = _("IP is in reserved class")
-            else:
-                msg = _("invalid IP, mustn't be in reserved class")
-            raise ValueError(msg)
-        if self._private_only and not ip.iptype() == 'PRIVATE':
-            if warnings_only:
-                msg = _("IP is not in private class")
-            else:
-                msg = _("invalid IP, must be in private class")
-            raise ValueError(msg)
-
-    def _cons_in_network(self, 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)):
-            if warnings_only:
-                msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}'
-                        ' ({5})')
-            else:
-                msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with '
-                        'netmask {4} ({5})')
-            raise ValueError(msg.format(ip, opts[0]._name, network,
-                             opts[1]._name, netmask, opts[2]._name))
-
-
-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__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
-    _opt_type = '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):
-        self._allow_range = allow_range
-        self._min_value = None
-        self._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 self._min_value is None:
-                if allowed:
-                    self._min_value = ports_min[index]
-            elif not allowed:
-                is_finally = True
-            elif allowed and is_finally:
-                raise ValueError(_('inconsistency in allowed range'))
-            if allowed:
-                self._max_value = ports_max[index]
-
-        if self._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)
-
-    def _validate(self, value):
-        if self._allow_range and ":" in str(value):
-            value = str(value).split(':')
-            if len(value) != 2:
-                raise ValueError(_('invalid port, range must have two values '
-                                 'only'))
-            if not value[0] < value[1]:
-                raise ValueError(_('invalid port, first port in range must be'
-                                 ' smaller than the second one'))
-        else:
-            value = [value]
-
-        for val in value:
-            try:
-                int(val)
-            except ValueError:
-                raise ValueError(_('invalid port'))
-            if not self._min_value <= int(val) <= self._max_value:
-                raise ValueError(_('invalid port, must be an between {0} '
-                                   'and {1}').format(self._min_value,
-                                                     self._max_value))
-
-
-class NetworkOption(Option):
-    "represents the choice of a network"
-    __slots__ = tuple()
-    _opt_type = 'network'
-
-    def _validate(self, value):
-        try:
-            IP(value)
-        except ValueError:
-            raise ValueError(_('invalid network address'))
-
-    def _second_level_validation(self, value, warnings_only):
-        ip = IP(value)
-        if ip.iptype() == 'RESERVED':
-            if warnings_only:
-                msg = _("network address is in reserved class")
-            else:
-                msg = _("invalid network address, mustn't be in reserved class")
-            raise ValueError(msg)
-
-
-class NetmaskOption(Option):
-    "represents the choice of a netmask"
-    __slots__ = tuple()
-    _opt_type = 'netmask'
-
-    def _validate(self, value):
-        try:
-            IP('0.0.0.0/{0}'.format(value))
-        except ValueError:
-            raise ValueError(_('invalid netmask address'))
-
-    def _cons_network_netmask(self, opts, vals, warnings_only):
-        #opts must be (netmask, network) options
-        if None in vals:
-            return
-        self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only)
-
-    def _cons_ip_netmask(self, opts, vals, warnings_only):
-        #opts must be (netmask, ip) options
-        if None in vals:
-            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:
-            raise 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 ip.prefixlen() != 32:
-                try:
-                    IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
-                        make_net=not make_net)
-                except ValueError:
-                    pass
-                else:
-                    if make_net:
-                        msg = _("invalid IP {0} ({1}) with netmask {2},"
-                                " this IP is a network")
-
-        except ValueError:
-            if not make_net:
-                msg = _('invalid network {0} ({1}) with netmask {2}')
-        if msg is not None:
-            raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
-                                        val_netmask))
-
-
-class BroadcastOption(Option):
-    __slots__ = tuple()
-    _opt_type = 'broadcast'
-
-    def _validate(self, value):
-        try:
-            IP('{0}/32'.format(value))
-        except ValueError:
-            raise ValueError(_('invalid broadcast address'))
-
-    def _cons_broadcast(self, 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):
-            raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
-                               '({3}) and netmask {4} ({5})').format(
-                                   broadcast, opts[0]._name, network,
-                                   opts[1]._name, netmask, opts[2]._name))
-
-
-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__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
-    _opt_type = 'domainname'
-
-    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_))
-        self._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'))
-        self._allow_ip = allow_ip
-        self._allow_without_dot = allow_without_dot
-        end = ''
-        extrachar = ''
-        extrachar_mandatory = ''
-        if self._type != 'netbios':
-            allow_number = '\d'
-        else:
-            allow_number = ''
-        if self._type == 'netbios':
-            length = 14
-        elif self._type == 'hostname':
-            length = 62
-        elif self._type == 'domainname':
-            length = 62
-            if allow_without_dot is False:
-                extrachar_mandatory = '\.'
-            else:
-                extrachar = '\.'
-            end = '+[a-z]*'
-        self._domain_re = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
-                                     ''.format(allow_number, extrachar, length,
-                                               extrachar_mandatory, end))
-        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)
-
-    def _validate(self, value):
-        if self._allow_ip is True:
-            try:
-                IP('{0}/32'.format(value))
-                return
-            except ValueError:
-                pass
-        if self._type == 'domainname' and not self._allow_without_dot and \
-                '.' not in value:
-            raise ValueError(_("invalid domainname, must have dot"))
-        if len(value) > 255:
-            raise ValueError(_("invalid domainname's length (max 255)"))
-        if len(value) < 2:
-            raise ValueError(_("invalid domainname's length (min 2)"))
-        if not self._domain_re.search(value):
-            raise ValueError(_('invalid domainname'))
-
-
-class EmailOption(DomainnameOption):
-    __slots__ = tuple()
-    _opt_type = 'email'
-    username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
-
-    def _validate(self, value):
-        splitted = value.split('@', 1)
-        try:
-            username, domain = splitted
-        except ValueError:
-            raise ValueError(_('invalid email address, must contains one @'
-                               ))
-        if not self.username_re.search(username):
-            raise ValueError(_('invalid username in email address'))
-        super(EmailOption, self)._validate(domain)
-
-
-class URLOption(DomainnameOption):
-    __slots__ = tuple()
-    _opt_type = 'url'
-    proto_re = re.compile(r'(http|https)://')
-    path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
-
-    def _validate(self, value):
-        match = self.proto_re.search(value)
-        if not match:
-            raise ValueError(_('invalid url, must start with http:// or '
-                               'https://'))
-        value = value[len(match.group(0)):]
-        # get domain/files
-        splitted = value.split('/', 1)
-        try:
-            domain, files = splitted
-        except ValueError:
-            domain = value
-            files = None
-        # if port in domain
-        splitted = domain.split(':', 1)
-        try:
-            domain, port = splitted
-
-        except ValueError:
-            domain = splitted[0]
-            port = 0
-        if not 0 <= int(port) <= 65535:
-            raise ValueError(_('invalid url, port must be an between 0 and '
-                               '65536'))
-        # validate domainname
-        super(URLOption, self)._validate(domain)
-        # validate file
-        if files is not None and files != '' and not self.path_re.search(files):
-            raise ValueError(_('invalid url, must ends with filename'))
-
-
-class UsernameOption(Option):
-    __slots__ = tuple()
-    _opt_type = 'username'
-    #regexp build with 'man 8 adduser' informations
-    username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
-
-    def _validate(self, value):
-        match = self.username_re.search(value)
-        if not match:
-            raise ValueError(_('invalid username'))
-
-
-class FilenameOption(Option):
-    __slots__ = tuple()
-    _opt_type = 'file'
-    path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
-
-    def _validate(self, value):
-        match = self.path_re.search(value)
-        if not match:
-            raise ValueError(_('invalid filename'))
-
-
-class OptionDescription(BaseOption):
-    """Config's schema (organisation, group) and container of Options
-    The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
-    """
-    __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
-                 '_state_group_type', '_properties', '_children',
-                 '_cache_consistencies', '_calc_properties', '__weakref__',
-                 '_readonly', '_impl_informations', '_state_requires',
-                 '_stated', '_state_readonly')
-    _opt_type = 'optiondescription'
-
-    def __init__(self, name, doc, children, requires=None, properties=None):
-        """
-        :param children: a list of options (including optiondescriptions)
-
-        """
-        super(OptionDescription, self).__init__(name, doc, requires, properties)
-        child_names = [child._name for child in children]
-        #better performance like this
-        valid_child = copy(child_names)
-        valid_child.sort()
-        old = None
-        for child in valid_child:
-            if child == old:
-                raise ConflictError(_('duplicate option name: '
-                                      '{0}').format(child))
-            old = child
-        self._children = (tuple(child_names), tuple(children))
-        self._cache_paths = None
-        self._cache_consistencies = None
-        # the group_type is useful for filtering OptionDescriptions in a config
-        self._group_type = groups.default
-
-    def impl_getdoc(self):
-        return self.impl_get_information('doc')
-
-    def __getattr__(self, name):
-        if name in self.__slots__:
-            return object.__getattribute__(self, name)
-        try:
-            return self._children[1][self._children[0].index(name)]
-        except ValueError:
-            raise AttributeError(_('unknown Option {0} '
-                                   'in OptionDescription {1}'
-                                   '').format(name, self._name))
-
-    def impl_getkey(self, config):
-        return tuple([child.impl_getkey(getattr(config, child._name))
-                      for child in self.impl_getchildren()])
-
-    def impl_getpaths(self, include_groups=False, _currpath=None):
-        """returns a list of all paths in self, recursively
-           _currpath should not be provided (helps with recursion)
-        """
-        if _currpath is None:
-            _currpath = []
-        paths = []
-        for option in self.impl_getchildren():
-            attr = option._name
-            if isinstance(option, OptionDescription):
-                if include_groups:
-                    paths.append('.'.join(_currpath + [attr]))
-                paths += option.impl_getpaths(include_groups=include_groups,
-                                              _currpath=_currpath + [attr])
-            else:
-                paths.append('.'.join(_currpath + [attr]))
-        return paths
-
-    def impl_getchildren(self):
-        return self._children[1]
-
-    def impl_build_cache(self,
-                         cache_path=None,
-                         cache_option=None,
-                         _currpath=None,
-                         _consistencies=None,
-                         force_no_consistencies=False):
-        if _currpath is None and self._cache_paths is not None:
-            # cache already set
-            return
-        if _currpath is None:
-            save = True
-            _currpath = []
-            if not force_no_consistencies:
-                _consistencies = {}
-        else:
-            save = False
-        if cache_path is None:
-            cache_path = []
-            cache_option = []
-        for option in self.impl_getchildren():
-            attr = option._name
-            if option in cache_option:
-                raise ConflictError(_('duplicate option: {0}').format(option))
-
-            cache_option.append(option)
-            if not force_no_consistencies:
-                option._readonly = True
-            cache_path.append(str('.'.join(_currpath + [attr])))
-            if not isinstance(option, OptionDescription):
-                if not force_no_consistencies and \
-                        option._consistencies is not None:
-                    for consistency in option._consistencies:
-                        func, all_cons_opts, params = consistency
-                        for opt in all_cons_opts:
-                            _consistencies.setdefault(opt,
-                                                      []).append((func,
-                                                                  all_cons_opts,
-                                                                  params))
-            else:
-                _currpath.append(attr)
-                option.impl_build_cache(cache_path,
-                                        cache_option,
-                                        _currpath,
-                                        _consistencies,
-                                        force_no_consistencies)
-                _currpath.pop()
-        if save:
-            self._cache_paths = (tuple(cache_option), tuple(cache_path))
-            if not force_no_consistencies:
-                if _consistencies != {}:
-                    self._cache_consistencies = {}
-                    for opt, cons in _consistencies.items():
-                        if opt not in cache_option:
-                            raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
-                        self._cache_consistencies[opt] = tuple(cons)
-                self._readonly = True
-
-    def impl_get_opt_by_path(self, path):
-        try:
-            return self._cache_paths[0][self._cache_paths[1].index(path)]
-        except ValueError:
-            raise AttributeError(_('no option for path {0}').format(path))
-
-    def impl_get_path_by_opt(self, opt):
-        try:
-            return self._cache_paths[1][self._cache_paths[0].index(opt)]
-        except ValueError:
-            raise AttributeError(_('no option {0} found').format(opt))
-
-    # ____________________________________________________________
-    def impl_set_group_type(self, group_type):
-        """sets a given group object to an OptionDescription
-
-        :param group_type: an instance of `GroupType` or `MasterGroupType`
-                              that lives in `setting.groups`
-        """
-        if self._group_type != groups.default:
-            raise TypeError(_('cannot change group_type if already set '
-                            '(old {0}, new {1})').format(self._group_type,
-                                                         group_type))
-        if isinstance(group_type, groups.GroupType):
-            self._group_type = group_type
-            if isinstance(group_type, groups.MasterGroupType):
-                #if master (same name has group) is set
-                #for collect all slaves
-                slaves = []
-                master = None
-                for child in self.impl_getchildren():
-                    if isinstance(child, OptionDescription):
-                        raise ValueError(_("master group {0} shall not have "
-                                         "a subgroup").format(self._name))
-                    if isinstance(child, SymLinkOption):
-                        raise ValueError(_("master group {0} shall not have "
-                                         "a symlinkoption").format(self._name))
-                    if not child.impl_is_multi():
-                        raise ValueError(_("not allowed option {0} "
-                                         "in group {1}"
-                                         ": this option is not a multi"
-                                         "").format(child._name, self._name))
-                    if child._name == self._name:
-                        child._multitype = multitypes.master
-                        master = child
-                    else:
-                        slaves.append(child)
-                if master is None:
-                    raise ValueError(_('master group with wrong'
-                                       ' master name for {0}'
-                                       ).format(self._name))
-                if master._callback is not None and master._callback[1] is not None:
-                    for key, callbacks in master._callback[1].items():
-                        for callbk in callbacks:
-                            if isinstance(callbk, tuple):
-                                if callbk[0] in slaves:
-                                    raise ValueError(_("callback of master's option shall "
-                                                       "not refered a slave's ones"))
-                master._master_slaves = tuple(slaves)
-                for child in self.impl_getchildren():
-                    if child != master:
-                        child._master_slaves = master
-                        child._multitype = multitypes.slave
-        else:
-            raise ValueError(_('group_type: {0}'
-                               ' not allowed').format(group_type))
-
-    def impl_get_group_type(self):
-        return self._group_type
-
-    def _valid_consistency(self, option, value, context, index):
-        if self._cache_consistencies is None:
-            return True
-        #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
-        consistencies = self._cache_consistencies.get(option)
-        if consistencies is not None:
-            for func, all_cons_opts, params in consistencies:
-                warnings_only = params.get('warnings_only', False)
-                #all_cons_opts[0] is the option where func is set
-                try:
-                    all_cons_opts[0]._launch_consistency(func, option,
-                                                         value,
-                                                         context, index,
-                                                         all_cons_opts,
-                                                         warnings_only)
-                except ValueError as err:
-                    if warnings_only:
-                        raise ValueWarning(err.message, option)
-                    else:
-                        raise err
-
-    def _impl_getstate(self, descr=None):
-        """enables us to export into a dict
-        :param descr: parent :class:`tiramisu.option.OptionDescription`
-        """
-        if descr is None:
-            self.impl_build_cache()
-            descr = self
-        super(OptionDescription, self)._impl_getstate(descr)
-        self._state_group_type = str(self._group_type)
-        for option in self.impl_getchildren():
-            option._impl_getstate(descr)
-
-    def __getstate__(self):
-        """special method to enable the serialization with pickle
-        """
-        stated = True
-        try:
-            # the `_state` attribute is a flag that which tells us if
-            # the serialization can be performed
-            self._stated
-        except AttributeError:
-            # if cannot delete, _impl_getstate never launch
-            # launch it recursivement
-            # _stated prevent __getstate__ launch more than one time
-            # _stated is delete, if re-serialize, re-lauch _impl_getstate
-            self._impl_getstate()
-            stated = False
-        return super(OptionDescription, self).__getstate__(stated)
-
-    def _impl_setstate(self, descr=None):
-        """enables us to import from a dict
-        :param descr: parent :class:`tiramisu.option.OptionDescription`
-        """
-        if descr is None:
-            self._cache_paths = None
-            self._cache_consistencies = None
-            self.impl_build_cache(force_no_consistencies=True)
-            descr = self
-        self._group_type = getattr(groups, self._state_group_type)
-        del(self._state_group_type)
-        super(OptionDescription, self)._impl_setstate(descr)
-        for option in self.impl_getchildren():
-            option._impl_setstate(descr)
-
-    def __setstate__(self, state):
-        super(OptionDescription, self).__setstate__(state)
-        try:
-            self._stated
-        except AttributeError:
-            self._impl_setstate()
-
-
-def validate_requires_arg(requires, name):
-    """check malformed requirements
-    and tranform dict to internal tuple
-
-    :param requires: have a look at the
-                     :meth:`tiramisu.setting.Settings.apply_requires` method to
-                     know more about
-                     the description of the requires dictionary
-    """
-    if requires is None:
-        return None, None
-    ret_requires = {}
-    config_action = {}
-
-    # start parsing all requires given by user (has dict)
-    # transforme it to a tuple
-    for require in requires:
-        if not type(require) == dict:
-            raise ValueError(_("malformed requirements type for option:"
-                               " {0}, must be a dict").format(name))
-        valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
-                      'same_action')
-        unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
-        if unknown_keys != frozenset():
-            raise ValueError('malformed requirements for option: {0}'
-                             ' unknown keys {1}, must only '
-                             '{2}'.format(name,
-                                          unknown_keys,
-                                          valid_keys))
-        # prepare all attributes
-        try:
-            option = require['option']
-            expected = require['expected']
-            action = require['action']
-        except KeyError:
-            raise ValueError(_("malformed requirements for option: {0}"
-                               " require must have option, expected and"
-                               " action keys").format(name))
-        if action == 'force_store_value':
-            raise ValueError(_("malformed requirements for option: {0}"
-                               " action cannot be force_store_value"
-                               ).format(name))
-        inverse = require.get('inverse', False)
-        if inverse not in [True, False]:
-            raise ValueError(_('malformed requirements for option: {0}'
-                               ' inverse must be boolean'))
-        transitive = require.get('transitive', True)
-        if transitive not in [True, False]:
-            raise ValueError(_('malformed requirements for option: {0}'
-                               ' transitive must be boolean'))
-        same_action = require.get('same_action', True)
-        if same_action not in [True, False]:
-            raise ValueError(_('malformed requirements for option: {0}'
-                               ' same_action must be boolean'))
-
-        if not isinstance(option, Option):
-            raise ValueError(_('malformed requirements '
-                               'must be an option in option {0}').format(name))
-        if option.impl_is_multi():
-            raise ValueError(_('malformed requirements option {0} '
-                               'must not be a multi').format(name))
-        if expected is not None:
-            try:
-                option._validate(expected)
-            except ValueError as err:
-                raise ValueError(_('malformed requirements second argument '
-                                   'must be valid for option {0}'
-                                   ': {1}').format(name, err))
-        if action in config_action:
-            if inverse != config_action[action]:
-                raise ValueError(_("inconsistency in action types"
-                                   " for option: {0}"
-                                   " action: {1}").format(name, action))
-        else:
-            config_action[action] = inverse
-        if action not in ret_requires:
-            ret_requires[action] = {}
-        if option not in ret_requires[action]:
-            ret_requires[action][option] = (option, [expected], action,
-                                            inverse, transitive, same_action)
-        else:
-            ret_requires[action][option][1].append(expected)
-    # transform dict to tuple
-    ret = []
-    for opt_requires in ret_requires.values():
-        ret_action = []
-        for require in opt_requires.values():
-            ret_action.append((require[0], tuple(require[1]), require[2],
-                               require[3], require[4], require[5]))
-        ret.append(tuple(ret_action))
-    return frozenset(config_action.keys()), tuple(ret)
-
-
-def validate_callback(callback, callback_params, type_):
-    if type(callback) != FunctionType:
-        raise ValueError(_('{0} must be a function').format(type_))
-    if callback_params is not None:
-        if not isinstance(callback_params, dict):
-            raise ValueError(_('{0}_params must be a dict').format(type_))
-        for key, callbacks in callback_params.items():
-            if key != '' and len(callbacks) != 1:
-                raise ValueError(_("{0}_params with key {1} mustn't have "
-                                   "length different to 1").format(type_,
-                                                                   key))
-            if not isinstance(callbacks, tuple):
-                raise ValueError(_('{0}_params must be tuple for key "{1}"'
-                                   ).format(type_, key))
-            for callbk in callbacks:
-                if isinstance(callbk, tuple):
-                    if len(callbk) == 1:
-                        if callbk != (None,):
-                            raise ValueError(_('{0}_params with length of '
-                                               'tuple as 1 must only have '
-                                               'None as first value'))
-                    elif len(callbk) != 2:
-                        raise ValueError(_('{0}_params must only have 1 or 2 '
-                                           'as length'))
-                    else:
-                        option, force_permissive = callbk
-                        if type_ == 'validator' and not force_permissive:
-                            raise ValueError(_('validator not support tuple'))
-                        if not isinstance(option, Option) and not \
-                                isinstance(option, SymLinkOption):
-                            raise ValueError(_('{0}_params must have an option'
-                                               ' not a {0} for first argument'
-                                               ).format(type_, type(option)))
-                        if force_permissive not in [True, False]:
-                            raise ValueError(_('{0}_params must have a boolean'
-                                               ' not a {0} for second argument'
-                                               ).format(type_, type(
-                                                   force_permissive)))
diff --git a/tiramisu/option/__init__.py b/tiramisu/option/__init__.py
new file mode 100644 (file)
index 0000000..1a325f8
--- /dev/null
@@ -0,0 +1,16 @@
+from .masterslave import MasterSlaves
+from .optiondescription import OptionDescription
+from .baseoption import Option, SymLinkOption
+from .option import (ChoiceOption, BoolOption, IntOption, FloatOption,
+                     StrOption, UnicodeOption, IPOption, PortOption,
+                     NetworkOption, NetmaskOption, BroadcastOption,
+                     DomainnameOption, EmailOption, URLOption, UsernameOption,
+                     FilenameOption)
+
+
+__all__ = (MasterSlaves, OptionDescription, Option, SymLinkOption,
+           ChoiceOption, BoolOption, IntOption, FloatOption,
+           StrOption, UnicodeOption, IPOption, PortOption,
+           NetworkOption, NetmaskOption, BroadcastOption,
+           DomainnameOption, EmailOption, URLOption, UsernameOption,
+           FilenameOption)
diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py
new file mode 100644 (file)
index 0000000..1e62e8f
--- /dev/null
@@ -0,0 +1,808 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014 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 copy import copy, deepcopy
+from types import FunctionType
+import warnings
+
+from tiramisu.i18n import _
+from tiramisu.setting import log
+from tiramisu.autolib import carry_out_calculation
+from tiramisu.error import ConfigError, ValueWarning
+
+name_regexp = re.compile(r'^\d+')
+forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
+                   'make_dict', 'unwrap_from_path', 'read_only',
+                   'read_write', 'getowner', 'set_contexts')
+
+
+def valid_name(name):
+    "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
+    if not isinstance(name, str):
+        return False
+    if re.match(name_regexp, name) is None and not name.startswith('_') \
+            and name not in forbidden_names \
+            and not name.startswith('impl_') \
+            and not name.startswith('cfgimpl_'):
+        return True
+    else:
+        return False
+
+
+def validate_callback(callback, callback_params, type_):
+    if type(callback) != FunctionType:
+        raise ValueError(_('{0} must be a function').format(type_))
+    if callback_params is not None:
+        if not isinstance(callback_params, dict):
+            raise ValueError(_('{0}_params must be a dict').format(type_))
+        for key, callbacks in callback_params.items():
+            if key != '' and len(callbacks) != 1:
+                raise ValueError(_("{0}_params with key {1} mustn't have "
+                                   "length different to 1").format(type_,
+                                                                   key))
+            if not isinstance(callbacks, tuple):
+                raise ValueError(_('{0}_params must be tuple for key "{1}"'
+                                   ).format(type_, key))
+            for callbk in callbacks:
+                if isinstance(callbk, tuple):
+                    if len(callbk) == 1:
+                        if callbk != (None,):
+                            raise ValueError(_('{0}_params with length of '
+                                               'tuple as 1 must only have '
+                                               'None as first value'))
+                    elif len(callbk) != 2:
+                        raise ValueError(_('{0}_params must only have 1 or 2 '
+                                           'as length'))
+                    else:
+                        option, force_permissive = callbk
+                        if type_ == 'validator' and not force_permissive:
+                            raise ValueError(_('validator not support tuple'))
+                        if not isinstance(option, Option) and not \
+                                isinstance(option, SymLinkOption):
+                            raise ValueError(_('{0}_params must have an option'
+                                               ' not a {0} for first argument'
+                                               ).format(type_, type(option)))
+                        if force_permissive not in [True, False]:
+                            raise ValueError(_('{0}_params must have a boolean'
+                                               ' not a {0} for second argument'
+                                               ).format(type_, type(
+                                                   force_permissive)))
+#____________________________________________________________
+#
+
+
+class BaseOption(object):
+    """This abstract base class stands for attribute access
+    in options that have to be set only once, it is of course done in the
+    __setattr__ method
+    """
+    __slots__ = ('_name', '_requires', '_properties', '_readonly',
+                 '_calc_properties', '_impl_informations',
+                 '_state_readonly', '_state_requires', '_stated')
+
+    def __init__(self, name, doc, requires, properties):
+        if not valid_name(name):
+            raise ValueError(_("invalid name: {0} for option").format(name))
+        self._name = name
+        self._impl_informations = {}
+        self.impl_set_information('doc', doc)
+        self._calc_properties, self._requires = validate_requires_arg(
+            requires, self._name)
+        if properties is None:
+            properties = tuple()
+        if not isinstance(properties, tuple):
+            raise TypeError(_('invalid properties type {0} for {1},'
+                            ' must be a tuple').format(
+                                type(properties),
+                                self._name))
+        if self._calc_properties is not None and properties is not tuple():
+            set_forbidden_properties = set(properties) & self._calc_properties
+            if set_forbidden_properties != frozenset():
+                raise ValueError('conflict: properties already set in '
+                                 'requirement {0}'.format(
+                                     list(set_forbidden_properties)))
+        self._properties = properties  # 'hidden', 'disabled'...
+
+    def __setattr__(self, name, value):
+        """set once and only once some attributes in the option,
+        like `_name`. `_name` cannot be changed one the option and
+        pushed in the :class:`tiramisu.option.OptionDescription`.
+
+        if the attribute `_readonly` is set to `True`, the option is
+        "frozen" (which has noting to do with the high level "freeze"
+        propertie or "read_only" property)
+        """
+        if not name.startswith('_state') and not name.startswith('_cache'):
+            is_readonly = False
+            # never change _name
+            if name == '_name':
+                try:
+                    self._name
+                    #so _name is already set
+                    is_readonly = True
+                except:
+                    pass
+            elif name != '_readonly':
+                try:
+                    if self._readonly is True:
+                        is_readonly = True
+                except AttributeError:
+                    self._readonly = False
+            if is_readonly:
+                raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
+                                       " read-only").format(
+                                           self.__class__.__name__,
+                                           self._name,
+                                           name))
+        object.__setattr__(self, name, value)
+
+    # information
+    def impl_set_information(self, key, value):
+        """updates the information's attribute
+        (which is a dictionary)
+
+        :param key: information's key (ex: "help", "doc"
+        :param value: information's value (ex: "the help string")
+        """
+        self._impl_informations[key] = value
+
+    def impl_get_information(self, key, default=None):
+        """retrieves one information's item
+
+        :param key: the item string (ex: "help")
+        """
+        if key in self._impl_informations:
+            return self._impl_informations[key]
+        elif default is not None:
+            return default
+        else:
+            raise ValueError(_("information's item not found: {0}").format(
+                key))
+
+    def _impl_convert_requires(self, descr, load=False):
+        """export of the requires during the serialization process
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        :param load: `True` if we are at the init of the option description
+        :type load: bool
+        """
+        if not load and self._requires is None:
+            self._state_requires = None
+        elif load and self._state_requires is None:
+            self._requires = None
+            del(self._state_requires)
+        else:
+            if load:
+                _requires = self._state_requires
+            else:
+                _requires = self._requires
+            new_value = []
+            for requires in _requires:
+                new_requires = []
+                for require in requires:
+                    if load:
+                        new_require = [descr.impl_get_opt_by_path(require[0])]
+                    else:
+                        new_require = [descr.impl_get_path_by_opt(require[0])]
+                    new_require.extend(require[1:])
+                    new_requires.append(tuple(new_require))
+                new_value.append(tuple(new_requires))
+            if load:
+                del(self._state_requires)
+                self._requires = new_value
+            else:
+                self._state_requires = new_value
+
+    # serialize
+    def _impl_getstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+
+        :param descr: the parent :class:`tiramisu.option.OptionDescription`
+        """
+        self._stated = True
+        for func in dir(self):
+            if func.startswith('_impl_convert_'):
+                getattr(self, func)(descr)
+        self._state_readonly = self._readonly
+
+    def __getstate__(self, stated=True):
+        """special method to enable the serialization with pickle
+        Usualy, a `__getstate__` method does'nt need any parameter,
+        but somme under the hood stuff need to be done before this action
+
+        :parameter stated: if stated is `True`, the serialization protocol
+                           can be performed, not ready yet otherwise
+        :parameter type: bool
+        """
+        try:
+            self._stated
+        except AttributeError:
+            raise SystemError(_('cannot serialize Option, '
+                                'only in OptionDescription'))
+        slots = set()
+        for subclass in self.__class__.__mro__:
+            if subclass is not object:
+                slots.update(subclass.__slots__)
+        slots -= frozenset(['_cache_paths', '_cache_consistencies',
+                            '__weakref__'])
+        states = {}
+        for slot in slots:
+            # remove variable if save variable converted
+            # in _state_xxxx variable
+            if '_state' + slot not in slots:
+                if slot.startswith('_state'):
+                    # should exists
+                    states[slot] = getattr(self, slot)
+                    # remove _state_xxx variable
+                    self.__delattr__(slot)
+                else:
+                    try:
+                        states[slot] = getattr(self, slot)
+                    except AttributeError:
+                        pass
+        if not stated:
+            del(states['_stated'])
+        return states
+
+    # unserialize
+    def _impl_setstate(self, descr):
+        """the under the hood stuff that need to be done
+        before the serialization.
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        """
+        for func in dir(self):
+            if func.startswith('_impl_convert_'):
+                getattr(self, func)(descr, load=True)
+        try:
+            self._readonly = self._state_readonly
+            del(self._state_readonly)
+            del(self._stated)
+        except AttributeError:
+            pass
+
+    def __setstate__(self, state):
+        """special method that enables us to serialize (pickle)
+
+        Usualy, a `__setstate__` method does'nt need any parameter,
+        but somme under the hood stuff need to be done before this action
+
+        :parameter state: a dict is passed to the loads, it is the attributes
+                          of the options object
+        :type state: dict
+        """
+        for key, value in state.items():
+            setattr(self, key, value)
+
+
+class Option(BaseOption):
+    """
+    Abstract base class for configuration option's.
+
+    Reminder: an Option object is **not** a container for the value.
+    """
+    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
+                 '_state_callback', '_callback', '_consistencies',
+                 '_warnings_only', '_master_slaves', '_state_consistencies',
+                 '__weakref__')
+    _empty = ''
+
+    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, warnings_only=False):
+        """
+        :param name: the option's name
+        :param doc: the option's description
+        :param default: specifies the default value of the option,
+                        for a multi : ['bla', 'bla', 'bla']
+        :param default_multi: 'bla' (used in case of a reset to default only at
+                        a given index)
+        :param requires: is a list of names of options located anywhere
+                         in the configuration.
+        :param multi: if true, the option's value is a list
+        :param callback: the name of a function. If set, the function's output
+                         is responsible of the option's value
+        :param callback_params: the callback's parameter
+        :param validator: the name of a function which stands for a custom
+                          validation of the value
+        :param validator_params: the validator's parameters
+        :param properties: tuple of default properties
+        :param warnings_only: _validator and _consistencies don't raise if True
+                             Values()._warning contain message
+
+        """
+        super(Option, self).__init__(name, doc, requires, properties)
+        self._multi = multi
+        if validator is not None:
+            validate_callback(validator, validator_params, 'validator')
+            self._validator = (validator, validator_params)
+        else:
+            self._validator = None
+        if not self._multi and default_multi is not None:
+            raise ValueError(_("a default_multi is set whereas multi is False"
+                             " in option: {0}").format(name))
+        if default_multi is not None:
+            try:
+                self._validate(default_multi)
+            except ValueError as err:
+                raise ValueError(_("invalid default_multi value {0} "
+                                   "for option {1}: {2}").format(
+                                       str(default_multi), name, err))
+        if callback is not None and (default is not None or
+                                     default_multi is not None):
+            raise ValueError(_("default value not allowed if option: {0} "
+                             "is calculated").format(name))
+        if callback is None and callback_params is not None:
+            raise ValueError(_("params defined for a callback function but "
+                             "no callback defined"
+                             " yet for option {0}").format(name))
+        if callback is not None:
+            validate_callback(callback, callback_params, 'callback')
+            self._callback = (callback, callback_params)
+        else:
+            self._callback = None
+        if self._multi:
+            if default is None:
+                default = []
+            self._default_multi = default_multi
+        self._warnings_only = warnings_only
+        self.impl_validate(default)
+        self._default = default
+        self._consistencies = None
+
+    def _launch_consistency(self, func, option, value, context, index,
+                            all_cons_opts, warnings_only):
+        """Launch consistency now
+
+        :param func: function name, this name should start with _cons_
+        :type func: `str`
+        :param option: option that value is changing
+        :type option: `tiramisu.option.Option`
+        :param value: new value of this option
+        :param context: Config's context, if None, check default value instead
+        :type context: `tiramisu.config.Config`
+        :param index: only for multi option, consistency should be launch for
+                      specified index
+        :type index: `int`
+        :param all_cons_opts: all options concerne by this consistency
+        :type all_cons_opts: `list` of `tiramisu.option.Option`
+        :param warnings_only: specific raise error for warning
+        :type warnings_only: `boolean`
+        """
+        if context is not None:
+            descr = context.cfgimpl_get_description()
+
+        all_cons_vals = []
+        for opt in all_cons_opts:
+            #get value
+            if option == opt:
+                opt_value = value
+            else:
+                #if context, calculate value, otherwise get default value
+                if context is not None:
+                    opt_value = context.getattr(
+                        descr.impl_get_path_by_opt(opt), validate=False,
+                        force_permissive=True)
+                else:
+                    opt_value = opt.impl_getdefault()
+
+            #append value
+            if not self.impl_is_multi() or option == opt:
+                all_cons_vals.append(opt_value)
+            else:
+                #value is not already set, could be higher index
+                try:
+                    all_cons_vals.append(opt_value[index])
+                except IndexError:
+                    #so return if no value
+                    return
+        getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
+
+    def impl_validate(self, value, context=None, validate=True,
+                      force_index=None):
+        """
+        :param value: the option's value
+        :param context: Config's context
+        :type context: :class:`tiramisu.config.Config`
+        :param validate: if true enables ``self._validator`` validation
+        :type validate: boolean
+        :param force_index: if multi, value has to be a list
+                               not if force_index is not None
+        :type force_index: integer
+        """
+        if not validate:
+            return
+
+        def val_validator(val):
+            if self._validator is not None:
+                if self._validator[1] is not None:
+                    validator_params = deepcopy(self._validator[1])
+                    if '' in validator_params:
+                        lst = list(validator_params[''])
+                        lst.insert(0, val)
+                        validator_params[''] = tuple(lst)
+                    else:
+                        validator_params[''] = (val,)
+                else:
+                    validator_params = {'': (val,)}
+                # Raise ValueError if not valid
+                carry_out_calculation(self, config=context,
+                                      callback=self._validator[0],
+                                      callback_params=validator_params)
+
+        def do_validation(_value, _index=None):
+            if _value is None:
+                return
+            # option validation
+            try:
+                self._validate(_value)
+            except ValueError as err:
+                log.debug('do_validation: value: {0} index: {1}'.format(
+                    _value, _index), exc_info=True)
+                raise ValueError(_('invalid value for option {0}: {1}'
+                                   '').format(self._name, err))
+            error = None
+            warning = None
+            try:
+                # valid with self._validator
+                val_validator(_value)
+                self._second_level_validation(_value, self._warnings_only)
+            except ValueError as error:
+                log.debug(_('do_validation for {0}: error in value').format(
+                    self._name), exc_info=True)
+                if self._warnings_only:
+                    warning = error
+                    error = None
+            except ValueWarning as warning:
+                log.debug(_('do_validation for {0}: warning in value').format(
+                    self._name), exc_info=True)
+                pass
+            if error is None and warning is None:
+                try:
+                    # if context launch consistency validation
+                    if context is not None:
+                        descr._valid_consistency(self, _value, context, _index)
+                except ValueError as error:
+                    log.debug(_('do_validation for {0}: error in consistency').format(
+                        self._name), exc_info=True)
+                    pass
+                except ValueWarning as warning:
+                    log.debug(_('do_validation for {0}: warning in consistency').format(
+                        self._name), exc_info=True)
+                    pass
+            if warning:
+                msg = _("warning on the value of the option {0}: {1}").format(
+                    self._name, warning)
+                warnings.warn_explicit(ValueWarning(msg, self),
+                                       ValueWarning,
+                                       self.__class__.__name__, 0)
+            elif error:
+                raise ValueError(_("invalid value for option {0}: {1}").format(
+                    self._name, error))
+
+        # generic calculation
+        if context is not None:
+            descr = context.cfgimpl_get_description()
+
+        if not self._multi or force_index is not None:
+            do_validation(value, force_index)
+        else:
+            if not isinstance(value, list):
+                raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self._name))
+            for index, val in enumerate(value):
+                do_validation(val, index)
+
+    def impl_getdefault(self):
+        "accessing the default value"
+        if isinstance(self._default, list):
+            return copy(self._default)
+        return self._default
+
+    def impl_getdefault_multi(self):
+        "accessing the default value for a multi"
+        return self._default_multi
+
+    def impl_is_master_slaves(self, type_='both'):
+        """FIXME
+        """
+        try:
+            self._master_slaves
+            if type_ in ('both', 'master') and \
+                    self._master_slaves.is_master(self):
+                return True
+            if type_ in ('both', 'slave') and \
+                    not self._master_slaves.is_master(self):
+                return True
+        except:
+            pass
+        return False
+
+    def impl_get_master_slaves(self):
+        return self._master_slaves
+
+    def impl_is_empty_by_default(self):
+        "no default value has been set yet"
+        if ((not self.impl_is_multi() and self._default is None) or
+                (self.impl_is_multi() and (self._default == []
+                                           or None in self._default))):
+            return True
+        return False
+
+    def impl_getdoc(self):
+        "accesses the Option's doc"
+        return self.impl_get_information('doc')
+
+    def impl_has_callback(self):
+        "to know if a callback has been defined or not"
+        if self._callback is None:
+            return False
+        else:
+            return True
+
+    def impl_getkey(self, value):
+        return value
+
+    def impl_is_multi(self):
+        return self._multi
+
+    def impl_add_consistency(self, func, *other_opts, **params):
+        """Add consistency means that value will be validate with other_opts
+        option's values.
+
+        :param func: function's name
+        :type func: `str`
+        :param other_opts: options used to validate value
+        :type other_opts: `list` of `tiramisu.option.Option`
+        :param params: extra params (only warnings_only are allowed)
+        """
+        if self._consistencies is None:
+            self._consistencies = []
+        warnings_only = params.get('warnings_only', False)
+        for opt in other_opts:
+            if not isinstance(opt, Option):
+                raise ConfigError(_('consistency must be set with an option'))
+            if self is opt:
+                raise ConfigError(_('cannot add consistency with itself'))
+            if self.impl_is_multi() != opt.impl_is_multi():
+                raise ConfigError(_('every options in consistency must be '
+                                    'multi or none'))
+        func = '_cons_{0}'.format(func)
+        all_cons_opts = tuple([self] + list(other_opts))
+        value = self.impl_getdefault()
+        if value is not None:
+            if self.impl_is_multi():
+                for idx, val in enumerate(value):
+                    self._launch_consistency(func, self, val, None,
+                                             idx, all_cons_opts, warnings_only)
+            else:
+                self._launch_consistency(func, self, value, None,
+                                         None, all_cons_opts, warnings_only)
+        self._consistencies.append((func, all_cons_opts, params))
+        self.impl_validate(self.impl_getdefault())
+
+    def _cons_not_equal(self, opts, vals, warnings_only):
+        for idx_inf, val_inf in enumerate(vals):
+            for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
+                if val_inf == val_sup is not None:
+                    if warnings_only:
+                        msg = _("same value for {0} and {1}, should be different")
+                    else:
+                        msg = _("same value for {0} and {1}, must be different")
+                    raise ValueError(msg.format(opts[idx_inf]._name,
+                                                opts[idx_inf + idx_sup + 1]._name))
+
+    def _impl_convert_callbacks(self, descr, load=False):
+        if not load and self._callback is None:
+            self._state_callback = None
+        elif load and self._state_callback is None:
+            self._callback = None
+            del(self._state_callback)
+        else:
+            if load:
+                callback, callback_params = self._state_callback
+            else:
+                callback, callback_params = self._callback
+            if callback_params is not None:
+                cllbck_prms = {}
+                for key, values in callback_params.items():
+                    vls = []
+                    for value in values:
+                        if isinstance(value, tuple):
+                            if load:
+                                value = (descr.impl_get_opt_by_path(value[0]),
+                                         value[1])
+                            else:
+                                value = (descr.impl_get_path_by_opt(value[0]),
+                                         value[1])
+                        vls.append(value)
+                    cllbck_prms[key] = tuple(vls)
+            else:
+                cllbck_prms = None
+
+            if load:
+                del(self._state_callback)
+                self._callback = (callback, cllbck_prms)
+            else:
+                self._state_callback = (callback, cllbck_prms)
+
+    # serialize/unserialize
+    def _impl_convert_consistencies(self, descr, load=False):
+        """during serialization process, many things have to be done.
+        one of them is the localisation of the options.
+        The paths are set once for all.
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        :param load: `True` if we are at the init of the option description
+        :type load: bool
+        """
+        if not load and self._consistencies is None:
+            self._state_consistencies = None
+        elif load and self._state_consistencies is None:
+            self._consistencies = None
+            del(self._state_consistencies)
+        else:
+            if load:
+                consistencies = self._state_consistencies
+            else:
+                consistencies = self._consistencies
+            new_value = []
+            for consistency in consistencies:
+                values = []
+                for obj in consistency[1]:
+                    if load:
+                        values.append(descr.impl_get_opt_by_path(obj))
+                    else:
+                        values.append(descr.impl_get_path_by_opt(obj))
+                new_value.append((consistency[0], tuple(values), consistency[2]))
+            if load:
+                del(self._state_consistencies)
+                self._consistencies = new_value
+            else:
+                self._state_consistencies = new_value
+
+    def _second_level_validation(self, value, warnings_only):
+        pass
+
+
+def validate_requires_arg(requires, name):
+    """check malformed requirements
+    and tranform dict to internal tuple
+
+    :param requires: have a look at the
+                     :meth:`tiramisu.setting.Settings.apply_requires` method to
+                     know more about
+                     the description of the requires dictionary
+    """
+    if requires is None:
+        return None, None
+    ret_requires = {}
+    config_action = {}
+
+    # start parsing all requires given by user (has dict)
+    # transforme it to a tuple
+    for require in requires:
+        if not type(require) == dict:
+            raise ValueError(_("malformed requirements type for option:"
+                               " {0}, must be a dict").format(name))
+        valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
+                      'same_action')
+        unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
+        if unknown_keys != frozenset():
+            raise ValueError('malformed requirements for option: {0}'
+                             ' unknown keys {1}, must only '
+                             '{2}'.format(name,
+                                          unknown_keys,
+                                          valid_keys))
+        # prepare all attributes
+        try:
+            option = require['option']
+            expected = require['expected']
+            action = require['action']
+        except KeyError:
+            raise ValueError(_("malformed requirements for option: {0}"
+                               " require must have option, expected and"
+                               " action keys").format(name))
+        if action == 'force_store_value':
+            raise ValueError(_("malformed requirements for option: {0}"
+                               " action cannot be force_store_value"
+                               ).format(name))
+        inverse = require.get('inverse', False)
+        if inverse not in [True, False]:
+            raise ValueError(_('malformed requirements for option: {0}'
+                               ' inverse must be boolean'))
+        transitive = require.get('transitive', True)
+        if transitive not in [True, False]:
+            raise ValueError(_('malformed requirements for option: {0}'
+                               ' transitive must be boolean'))
+        same_action = require.get('same_action', True)
+        if same_action not in [True, False]:
+            raise ValueError(_('malformed requirements for option: {0}'
+                               ' same_action must be boolean'))
+
+        if not isinstance(option, Option):
+            raise ValueError(_('malformed requirements '
+                               'must be an option in option {0}').format(name))
+        if option.impl_is_multi():
+            raise ValueError(_('malformed requirements option {0} '
+                               'must not be a multi').format(name))
+        if expected is not None:
+            try:
+                option._validate(expected)
+            except ValueError as err:
+                raise ValueError(_('malformed requirements second argument '
+                                   'must be valid for option {0}'
+                                   ': {1}').format(name, err))
+        if action in config_action:
+            if inverse != config_action[action]:
+                raise ValueError(_("inconsistency in action types"
+                                   " for option: {0}"
+                                   " action: {1}").format(name, action))
+        else:
+            config_action[action] = inverse
+        if action not in ret_requires:
+            ret_requires[action] = {}
+        if option not in ret_requires[action]:
+            ret_requires[action][option] = (option, [expected], action,
+                                            inverse, transitive, same_action)
+        else:
+            ret_requires[action][option][1].append(expected)
+    # transform dict to tuple
+    ret = []
+    for opt_requires in ret_requires.values():
+        ret_action = []
+        for require in opt_requires.values():
+            ret_action.append((require[0], tuple(require[1]), require[2],
+                               require[3], require[4], require[5]))
+        ret.append(tuple(ret_action))
+    return frozenset(config_action.keys()), tuple(ret)
+
+
+class SymLinkOption(BaseOption):
+    __slots__ = ('_name', '_opt', '_state_opt')
+    _opt_type = 'symlink'
+    #not return _opt consistencies
+    _consistencies = None
+
+    def __init__(self, name, opt):
+        self._name = name
+        if not isinstance(opt, Option):
+            raise ValueError(_('malformed symlinkoption '
+                               'must be an option '
+                               'for symlink {0}').format(name))
+        self._opt = opt
+        self._readonly = True
+
+    def __getattr__(self, name):
+        if name in ('_name', '_opt', '_opt_type', '_readonly'):
+            return object.__getattr__(self, name)
+        else:
+            return getattr(self._opt, name)
+
+    def _impl_getstate(self, descr):
+        super(SymLinkOption, self)._impl_getstate(descr)
+        self._state_opt = descr.impl_get_path_by_opt(self._opt)
+
+    def _impl_setstate(self, descr):
+        self._opt = descr.impl_get_opt_by_path(self._state_opt)
+        del(self._state_opt)
+        super(SymLinkOption, self)._impl_setstate(descr)
diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py
new file mode 100644 (file)
index 0000000..9e0426d
--- /dev/null
@@ -0,0 +1,187 @@
+# -*- coding: utf-8 -*-
+"master slave support"
+# Copyright (C) 2014 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 tiramisu.i18n import _
+from tiramisu.setting import log
+from tiramisu.error import SlaveError, ConfigError
+from .baseoption import SymLinkOption, Option
+
+
+class MasterSlaves(object):
+    __slots__ = ('master', 'slaves')
+
+    def __init__(self, name, childs):
+        #if master (same name has group) is set
+        #for collect all slaves
+        self.master = None
+        slaves = []
+        for child in childs:
+            if isinstance(child, SymLinkOption):
+                raise ValueError(_("master group {0} shall not have "
+                                   "a symlinkoption").format(name))
+            if not isinstance(child, Option):
+                raise ValueError(_("master group {0} shall not have "
+                                   "a subgroup").format(name))
+            if not child.impl_is_multi():
+                raise ValueError(_("not allowed option {0} "
+                                   "in group {1}"
+                                   ": this option is not a multi"
+                                   "").format(child._name, name))
+            if child._name == name:
+                self.master = child
+            else:
+                slaves.append(child)
+        if self.master is None:
+            raise ValueError(_('master group with wrong'
+                               ' master name for {0}'
+                               ).format(name))
+        if self.master._callback is not None and self.master._callback[1] is not None:
+            for key, callbacks in self.master._callback[1].items():
+                for callbk in callbacks:
+                    if isinstance(callbk, tuple):
+                        if callbk[0] in slaves:
+                            raise ValueError(_("callback of master's option shall "
+                                               "not refered a slave's ones"))
+        #everything is ok, store references
+        self.slaves = tuple(slaves)
+        for child in childs:
+            child._master_slaves = self
+
+    def is_master(self, opt):
+        return opt == self.master
+
+    def in_same_group(self, opt):
+        return opt == self.master or opt in self.slaves
+
+    def reset(self, values):
+        for slave in self.slaves:
+            values.reset(slave)
+
+    def pop(self, values, index):
+        #FIXME pas test de meta ...
+        for slave in self.slaves:
+            if not values.is_default_owner(slave, validate_properties=False,
+                                           validate_meta=False):
+                values._get_cached_item(slave, validate=False,
+                                        validate_properties=False
+                                        ).pop(index, force=True)
+        pass
+
+    def getitem(self, values, opt, path, validate, force_permissive,
+                force_properties, validate_properties):
+        if opt == self.master:
+            value = values._get_validated_value(opt, path, validate,
+                                                force_permissive,
+                                                force_properties,
+                                                validate_properties)
+            if validate is True:
+                masterlen = len(value)
+                for slave in self.slaves:
+                    try:
+                        slave_path = values._get_opt_path(slave)
+                        slave_value = values._get_validated_value(slave,
+                                                                  slave_path,
+                                                                  False,
+                                                                  False,
+                                                                  None, False,
+                                                                  None)  # not undefined
+                        slavelen = len(slave_value)
+                        self.validate_slave_length(masterlen, slavelen, slave._name)
+                    except ConfigError:
+                        pass
+            return value
+        else:
+            value = values._get_validated_value(opt, path, validate,
+                                                force_permissive,
+                                                force_properties,
+                                                validate_properties,
+                                                None)  # not undefined
+            return self.get_slave_value(values, opt, value, validate, validate_properties)
+
+    def setitem(self, values, opt, value, path):
+        if opt == self.master:
+            masterlen = len(value)
+            for slave in self.slaves:
+                slave_path = values._get_opt_path(slave)
+                slave_value = values._get_validated_value(slave,
+                                                          slave_path,
+                                                          False,
+                                                          False,
+                                                          None, False,
+                                                          None)  # not undefined
+                slavelen = len(slave_value)
+                self.validate_slave_length(masterlen, slavelen, slave._name)
+        else:
+            self.validate_slave_length(self.get_length(values), len(value),
+                                       opt._name, setitem=True)
+
+    def get_length(self, values, validate=True):
+        masterp = values._get_opt_path(self.master)
+        return len(self.getitem(values, self.master, masterp, validate, False,
+                                None, True))
+
+    def validate_slave_length(self, masterlen, valuelen, name, setitem=False):
+        if valuelen > masterlen or (valuelen < masterlen and setitem):
+            log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
+                      'setitem: {2}'.format(masterlen, valuelen, setitem))
+            raise SlaveError(_("invalid len for the slave: {0}"
+                               " which has {1} as master").format(
+                                   name, self.master._name))
+
+    def get_slave_value(self, values, opt, value, validate=True,
+                        validate_properties=True):
+        """
+        if master has length 0:
+            return []
+        if master has length bigger than 0:
+            if default owner:
+                if has callback:
+                    if return a list:
+                        list same length as master: return list
+                        list is smaller than master: return list + None
+                        list is greater than master: raise SlaveError
+                if has default value:
+                    list same length as master: return list
+                    list is smaller than master: return list + None
+                    list is greater than master: raise SlaveError
+                if has default_multi value:
+                    return default_multi * master's length
+            if has value:
+                list same length as master: return list
+                list is smaller than master: return list + None
+                list is greater than master: raise SlaveError
+        """
+        #if slave, had values until master's one
+        masterlen = self.get_length(values, validate)
+        valuelen = len(value)
+        if validate:
+            self.validate_slave_length(masterlen, valuelen, opt._name)
+        path = values._get_opt_path(opt)
+        if valuelen < masterlen:
+            for num in range(0, masterlen - valuelen):
+                index = valuelen + num
+                value.append(values._get_validated_value(opt, path, True,
+                                                         False, None,
+                                                         validate_properties,
+                                                         index=index),
+                             setitem=False,
+                             force=True)
+        return value
diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py
new file mode 100644 (file)
index 0000000..8a0cda6
--- /dev/null
@@ -0,0 +1,525 @@
+# -*- coding: utf-8 -*-
+"option types and option description"
+# Copyright (C) 2012-2013 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 IPy import IP
+
+from tiramisu.error import ConfigError
+from tiramisu.i18n import _
+from .baseoption import Option
+
+
+class ChoiceOption(Option):
+    """represents a choice out of several objects.
+
+    The option can also have the value ``None``
+    """
+
+    __slots__ = ('_values', '_open_values')
+    _opt_type = 'string'
+
+    def __init__(self, name, doc, values, default=None, default_multi=None,
+                 requires=None, multi=False, callback=None,
+                 callback_params=None, open_values=False, validator=None,
+                 validator_params=None, properties=None, warnings_only=False):
+        """
+        :param values: is a list of values the option can possibly take
+        """
+        if not isinstance(values, tuple):
+            raise TypeError(_('values must be a tuple for {0}').format(name))
+        self._values = values
+        if open_values not in (True, False):
+            raise TypeError(_('open_values must be a boolean for '
+                            '{0}').format(name))
+        self._open_values = open_values
+        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):
+        return self._values
+
+    def impl_is_openvalues(self):
+        return self._open_values
+
+    def _validate(self, value):
+        if not self.impl_is_openvalues() and not value in self.impl_get_values():
+            raise ValueError(_('value {0} is not permitted, '
+                               'only {1} is allowed'
+                               '').format(value, self._values))
+
+
+class BoolOption(Option):
+    "represents a choice between ``True`` and ``False``"
+    __slots__ = tuple()
+    _opt_type = 'bool'
+
+    def _validate(self, value):
+        if not isinstance(value, bool):
+            raise ValueError(_('invalid boolean'))
+
+
+class IntOption(Option):
+    "represents a choice of an integer"
+    __slots__ = tuple()
+    _opt_type = 'int'
+
+    def _validate(self, value):
+        if not isinstance(value, int):
+            raise ValueError(_('invalid integer'))
+
+
+class FloatOption(Option):
+    "represents a choice of a floating point number"
+    __slots__ = tuple()
+    _opt_type = 'float'
+
+    def _validate(self, value):
+        if not isinstance(value, float):
+            raise ValueError(_('invalid float'))
+
+
+class StrOption(Option):
+    "represents the choice of a string"
+    __slots__ = tuple()
+    _opt_type = 'string'
+
+    def _validate(self, value):
+        if not isinstance(value, str):
+            raise ValueError(_('invalid string'))
+
+
+if sys.version_info[0] >= 3:
+    #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()
+        _opt_type = 'unicode'
+        _empty = u''
+
+        def _validate(self, value):
+            if not isinstance(value, unicode):
+                raise ValueError(_('invalid unicode'))
+
+
+class IPOption(Option):
+    "represents the choice of an ip"
+    __slots__ = ('_private_only', '_allow_reserved')
+    _opt_type = '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):
+        self._private_only = private_only
+        self._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)
+
+    def _validate(self, value):
+        # sometimes an ip term starts with a zero
+        # but this does not fit in some case, for example bind does not like it
+        try:
+            for val in value.split('.'):
+                if val.startswith("0") and len(val) > 1:
+                    raise ValueError(_('invalid IP'))
+        except AttributeError:
+            #if integer for example
+            raise ValueError(_('invalid IP'))
+        # 'standard' validation
+        try:
+            IP('{0}/32'.format(value))
+        except ValueError:
+            raise ValueError(_('invalid IP'))
+
+    def _second_level_validation(self, value, warnings_only):
+        ip = IP('{0}/32'.format(value))
+        if not self._allow_reserved and ip.iptype() == 'RESERVED':
+            if warnings_only:
+                msg = _("IP is in reserved class")
+            else:
+                msg = _("invalid IP, mustn't be in reserved class")
+            raise ValueError(msg)
+        if self._private_only and not ip.iptype() == 'PRIVATE':
+            if warnings_only:
+                msg = _("IP is not in private class")
+            else:
+                msg = _("invalid IP, must be in private class")
+            raise ValueError(msg)
+
+    def _cons_in_network(self, 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)):
+            if warnings_only:
+                msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}'
+                        ' ({5})')
+            else:
+                msg = _('invalid IP {0} ({1}) not in network {2} ({3}) with '
+                        'netmask {4} ({5})')
+            raise ValueError(msg.format(ip, opts[0]._name, network,
+                             opts[1]._name, netmask, opts[2]._name))
+
+
+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__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
+    _opt_type = '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):
+        self._allow_range = allow_range
+        self._min_value = None
+        self._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 self._min_value is None:
+                if allowed:
+                    self._min_value = ports_min[index]
+            elif not allowed:
+                is_finally = True
+            elif allowed and is_finally:
+                raise ValueError(_('inconsistency in allowed range'))
+            if allowed:
+                self._max_value = ports_max[index]
+
+        if self._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)
+
+    def _validate(self, value):
+        if self._allow_range and ":" in str(value):
+            value = str(value).split(':')
+            if len(value) != 2:
+                raise ValueError(_('invalid port, range must have two values '
+                                 'only'))
+            if not value[0] < value[1]:
+                raise ValueError(_('invalid port, first port in range must be'
+                                 ' smaller than the second one'))
+        else:
+            value = [value]
+
+        for val in value:
+            try:
+                int(val)
+            except ValueError:
+                raise ValueError(_('invalid port'))
+            if not self._min_value <= int(val) <= self._max_value:
+                raise ValueError(_('invalid port, must be an between {0} '
+                                   'and {1}').format(self._min_value,
+                                                     self._max_value))
+
+
+class NetworkOption(Option):
+    "represents the choice of a network"
+    __slots__ = tuple()
+    _opt_type = 'network'
+
+    def _validate(self, value):
+        try:
+            IP(value)
+        except ValueError:
+            raise ValueError(_('invalid network address'))
+
+    def _second_level_validation(self, value, warnings_only):
+        ip = IP(value)
+        if ip.iptype() == 'RESERVED':
+            if warnings_only:
+                msg = _("network address is in reserved class")
+            else:
+                msg = _("invalid network address, mustn't be in reserved class")
+            raise ValueError(msg)
+
+
+class NetmaskOption(Option):
+    "represents the choice of a netmask"
+    __slots__ = tuple()
+    _opt_type = 'netmask'
+
+    def _validate(self, value):
+        try:
+            IP('0.0.0.0/{0}'.format(value))
+        except ValueError:
+            raise ValueError(_('invalid netmask address'))
+
+    def _cons_network_netmask(self, opts, vals, warnings_only):
+        #opts must be (netmask, network) options
+        if None in vals:
+            return
+        self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only)
+
+    def _cons_ip_netmask(self, opts, vals, warnings_only):
+        #opts must be (netmask, ip) options
+        if None in vals:
+            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:
+            raise 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 ip.prefixlen() != 32:
+                try:
+                    IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
+                        make_net=not make_net)
+                except ValueError:
+                    pass
+                else:
+                    if make_net:
+                        msg = _("invalid IP {0} ({1}) with netmask {2},"
+                                " this IP is a network")
+
+        except ValueError:
+            if not make_net:
+                msg = _('invalid network {0} ({1}) with netmask {2}')
+        if msg is not None:
+            raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
+                                        val_netmask))
+
+
+class BroadcastOption(Option):
+    __slots__ = tuple()
+    _opt_type = 'broadcast'
+
+    def _validate(self, value):
+        try:
+            IP('{0}/32'.format(value))
+        except ValueError:
+            raise ValueError(_('invalid broadcast address'))
+
+    def _cons_broadcast(self, 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):
+            raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
+                               '({3}) and netmask {4} ({5})').format(
+                                   broadcast, opts[0]._name, network,
+                                   opts[1]._name, netmask, opts[2]._name))
+
+
+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__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
+    _opt_type = 'domainname'
+
+    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_))
+        self._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'))
+        self._allow_ip = allow_ip
+        self._allow_without_dot = allow_without_dot
+        end = ''
+        extrachar = ''
+        extrachar_mandatory = ''
+        if self._type != 'netbios':
+            allow_number = '\d'
+        else:
+            allow_number = ''
+        if self._type == 'netbios':
+            length = 14
+        elif self._type == 'hostname':
+            length = 62
+        elif self._type == 'domainname':
+            length = 62
+            if allow_without_dot is False:
+                extrachar_mandatory = '\.'
+            else:
+                extrachar = '\.'
+            end = '+[a-z]*'
+        self._domain_re = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$'
+                                     ''.format(allow_number, extrachar, length,
+                                               extrachar_mandatory, end))
+        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)
+
+    def _validate(self, value):
+        if self._allow_ip is True:
+            try:
+                IP('{0}/32'.format(value))
+                return
+            except ValueError:
+                pass
+        if self._type == 'domainname' and not self._allow_without_dot and \
+                '.' not in value:
+            raise ValueError(_("invalid domainname, must have dot"))
+        if len(value) > 255:
+            raise ValueError(_("invalid domainname's length (max 255)"))
+        if len(value) < 2:
+            raise ValueError(_("invalid domainname's length (min 2)"))
+        if not self._domain_re.search(value):
+            raise ValueError(_('invalid domainname'))
+
+
+class EmailOption(DomainnameOption):
+    __slots__ = tuple()
+    _opt_type = 'email'
+    username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
+
+    def _validate(self, value):
+        splitted = value.split('@', 1)
+        try:
+            username, domain = splitted
+        except ValueError:
+            raise ValueError(_('invalid email address, must contains one @'
+                               ))
+        if not self.username_re.search(username):
+            raise ValueError(_('invalid username in email address'))
+        super(EmailOption, self)._validate(domain)
+
+
+class URLOption(DomainnameOption):
+    __slots__ = tuple()
+    _opt_type = 'url'
+    proto_re = re.compile(r'(http|https)://')
+    path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
+
+    def _validate(self, value):
+        match = self.proto_re.search(value)
+        if not match:
+            raise ValueError(_('invalid url, must start with http:// or '
+                               'https://'))
+        value = value[len(match.group(0)):]
+        # get domain/files
+        splitted = value.split('/', 1)
+        try:
+            domain, files = splitted
+        except ValueError:
+            domain = value
+            files = None
+        # if port in domain
+        splitted = domain.split(':', 1)
+        try:
+            domain, port = splitted
+
+        except ValueError:
+            domain = splitted[0]
+            port = 0
+        if not 0 <= int(port) <= 65535:
+            raise ValueError(_('invalid url, port must be an between 0 and '
+                               '65536'))
+        # validate domainname
+        super(URLOption, self)._validate(domain)
+        # validate file
+        if files is not None and files != '' and not self.path_re.search(files):
+            raise ValueError(_('invalid url, must ends with filename'))
+
+
+class UsernameOption(Option):
+    __slots__ = tuple()
+    _opt_type = 'username'
+    #regexp build with 'man 8 adduser' informations
+    username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$")
+
+    def _validate(self, value):
+        match = self.username_re.search(value)
+        if not match:
+            raise ValueError(_('invalid username'))
+
+
+class FilenameOption(Option):
+    __slots__ = tuple()
+    _opt_type = 'file'
+    path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
+
+    def _validate(self, value):
+        match = self.path_re.search(value)
+        if not match:
+            raise ValueError(_('invalid filename'))
diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py
new file mode 100644 (file)
index 0000000..8c26f3b
--- /dev/null
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2014 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 copy import copy
+
+from tiramisu.i18n import _
+from tiramisu.setting import groups, log
+from .baseoption import BaseOption
+from . import MasterSlaves
+from tiramisu.error import ConfigError, ConflictError, ValueWarning
+
+
+class OptionDescription(BaseOption):
+    """Config's schema (organisation, group) and container of Options
+    The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
+    """
+    __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
+                 '_state_group_type', '_properties', '_children',
+                 '_cache_consistencies', '_calc_properties', '__weakref__',
+                 '_readonly', '_impl_informations', '_state_requires',
+                 '_stated', '_state_readonly')
+    _opt_type = 'optiondescription'
+
+    def __init__(self, name, doc, children, requires=None, properties=None):
+        """
+        :param children: a list of options (including optiondescriptions)
+
+        """
+        super(OptionDescription, self).__init__(name, doc, requires, properties)
+        child_names = [child._name for child in children]
+        #better performance like this
+        valid_child = copy(child_names)
+        valid_child.sort()
+        old = None
+        for child in valid_child:
+            if child == old:
+                raise ConflictError(_('duplicate option name: '
+                                      '{0}').format(child))
+            old = child
+        self._children = (tuple(child_names), tuple(children))
+        self._cache_paths = None
+        self._cache_consistencies = None
+        # the group_type is useful for filtering OptionDescriptions in a config
+        self._group_type = groups.default
+
+    def impl_getdoc(self):
+        return self.impl_get_information('doc')
+
+    def __getattr__(self, name):
+        if name in self.__slots__:
+            return object.__getattribute__(self, name)
+        try:
+            return self._children[1][self._children[0].index(name)]
+        except ValueError:
+            log.debug('__getattr__', exc_info=True)
+            raise AttributeError(_('unknown Option {0} '
+                                   'in OptionDescription {1}'
+                                   '').format(name, self._name))
+
+    def impl_getkey(self, config):
+        return tuple([child.impl_getkey(getattr(config, child._name))
+                      for child in self.impl_getchildren()])
+
+    def impl_getpaths(self, include_groups=False, _currpath=None):
+        """returns a list of all paths in self, recursively
+           _currpath should not be provided (helps with recursion)
+        """
+        if _currpath is None:
+            _currpath = []
+        paths = []
+        for option in self.impl_getchildren():
+            attr = option._name
+            if isinstance(option, OptionDescription):
+                if include_groups:
+                    paths.append('.'.join(_currpath + [attr]))
+                paths += option.impl_getpaths(include_groups=include_groups,
+                                              _currpath=_currpath + [attr])
+            else:
+                paths.append('.'.join(_currpath + [attr]))
+        return paths
+
+    def impl_getchildren(self):
+        return self._children[1]
+
+    def impl_build_cache(self,
+                         cache_path=None,
+                         cache_option=None,
+                         _currpath=None,
+                         _consistencies=None,
+                         force_no_consistencies=False):
+        if _currpath is None and self._cache_paths is not None:
+            # cache already set
+            return
+        if _currpath is None:
+            save = True
+            _currpath = []
+            if not force_no_consistencies:
+                _consistencies = {}
+        else:
+            save = False
+        if cache_path is None:
+            cache_path = []
+            cache_option = []
+        for option in self.impl_getchildren():
+            attr = option._name
+            if option in cache_option:
+                raise ConflictError(_('duplicate option: {0}').format(option))
+
+            cache_option.append(option)
+            if not force_no_consistencies:
+                option._readonly = True
+            cache_path.append(str('.'.join(_currpath + [attr])))
+            if not isinstance(option, OptionDescription):
+                if not force_no_consistencies and \
+                        option._consistencies is not None:
+                    for consistency in option._consistencies:
+                        func, all_cons_opts, params = consistency
+                        for opt in all_cons_opts:
+                            _consistencies.setdefault(opt,
+                                                      []).append((func,
+                                                                  all_cons_opts,
+                                                                  params))
+            else:
+                _currpath.append(attr)
+                option.impl_build_cache(cache_path,
+                                        cache_option,
+                                        _currpath,
+                                        _consistencies,
+                                        force_no_consistencies)
+                _currpath.pop()
+        if save:
+            self._cache_paths = (tuple(cache_option), tuple(cache_path))
+            if not force_no_consistencies:
+                if _consistencies != {}:
+                    self._cache_consistencies = {}
+                    for opt, cons in _consistencies.items():
+                        if opt not in cache_option:
+                            raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
+                        self._cache_consistencies[opt] = tuple(cons)
+                self._readonly = True
+
+    def impl_get_opt_by_path(self, path):
+        try:
+            return self._cache_paths[0][self._cache_paths[1].index(path)]
+        except ValueError:
+            raise AttributeError(_('no option for path {0}').format(path))
+
+    def impl_get_path_by_opt(self, opt):
+        try:
+            return self._cache_paths[1][self._cache_paths[0].index(opt)]
+        except ValueError:
+            raise AttributeError(_('no option {0} found').format(opt))
+
+    # ____________________________________________________________
+    def impl_set_group_type(self, group_type):
+        """sets a given group object to an OptionDescription
+
+        :param group_type: an instance of `GroupType` or `MasterGroupType`
+                              that lives in `setting.groups`
+        """
+        if self._group_type != groups.default:
+            raise TypeError(_('cannot change group_type if already set '
+                            '(old {0}, new {1})').format(self._group_type,
+                                                         group_type))
+        if isinstance(group_type, groups.GroupType):
+            self._group_type = group_type
+            if isinstance(group_type, groups.MasterGroupType):
+                MasterSlaves(self._name, self.impl_getchildren())
+        else:
+            raise ValueError(_('group_type: {0}'
+                               ' not allowed').format(group_type))
+
+    def impl_get_group_type(self):
+        return self._group_type
+
+    def _valid_consistency(self, option, value, context, index):
+        if self._cache_consistencies is None:
+            return True
+        #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
+        consistencies = self._cache_consistencies.get(option)
+        if consistencies is not None:
+            for func, all_cons_opts, params in consistencies:
+                warnings_only = params.get('warnings_only', False)
+                #all_cons_opts[0] is the option where func is set
+                try:
+                    all_cons_opts[0]._launch_consistency(func, option,
+                                                         value,
+                                                         context, index,
+                                                         all_cons_opts,
+                                                         warnings_only)
+                except ValueError as err:
+                    if warnings_only:
+                        raise ValueWarning(err.message, option)
+                    else:
+                        raise err
+
+    def _impl_getstate(self, descr=None):
+        """enables us to export into a dict
+        :param descr: parent :class:`tiramisu.option.OptionDescription`
+        """
+        if descr is None:
+            self.impl_build_cache()
+            descr = self
+        super(OptionDescription, self)._impl_getstate(descr)
+        self._state_group_type = str(self._group_type)
+        for option in self.impl_getchildren():
+            option._impl_getstate(descr)
+
+    def __getstate__(self):
+        """special method to enable the serialization with pickle
+        """
+        stated = True
+        try:
+            # the `_state` attribute is a flag that which tells us if
+            # the serialization can be performed
+            self._stated
+        except AttributeError:
+            # if cannot delete, _impl_getstate never launch
+            # launch it recursivement
+            # _stated prevent __getstate__ launch more than one time
+            # _stated is delete, if re-serialize, re-lauch _impl_getstate
+            self._impl_getstate()
+            stated = False
+        return super(OptionDescription, self).__getstate__(stated)
+
+    def _impl_setstate(self, descr=None):
+        """enables us to import from a dict
+        :param descr: parent :class:`tiramisu.option.OptionDescription`
+        """
+        if descr is None:
+            self._cache_paths = None
+            self._cache_consistencies = None
+            self.impl_build_cache(force_no_consistencies=True)
+            descr = self
+        self._group_type = getattr(groups, self._state_group_type)
+        del(self._state_group_type)
+        super(OptionDescription, self)._impl_setstate(descr)
+        for option in self.impl_getchildren():
+            option._impl_setstate(descr)
+
+    def __setstate__(self, state):
+        super(OptionDescription, self).__setstate__(state)
+        try:
+            self._stated
+        except AttributeError:
+            self._impl_setstate()
index 95cee47..ded4108 100644 (file)
@@ -101,6 +101,9 @@ rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
 
 
 log = getLogger('tiramisu')
+#FIXME
+#import logging
+#logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
 
 
 # ____________________________________________________________
@@ -211,34 +214,12 @@ def populate_owners():
     setattr(owners, 'addowner', addowner)
 
 
-def populate_multitypes():
-    """all multi option should have a type, this type is automaticly set do
-    not touch this
-
-    default
-        default's multi option set if not master or slave
-
-    master
-        master's option in a group with master's type, name of this option
-        should be the same name of the optiondescription
-
-    slave
-        slave's option in a group with master's type
-
-    """
-    setattr(multitypes, 'default', multitypes.DefaultMultiType('default'))
-    setattr(multitypes, 'master', multitypes.MasterMultiType('master'))
-    setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave'))
-
-
 # ____________________________________________________________
 # populate groups, owners and multitypes with default attributes
 groups = GroupModule()
 populate_groups()
 owners = OwnerModule()
 populate_owners()
-multitypes = MultiTypeModule()
-populate_multitypes()
 
 
 # ____________________________________________________________
index 87b0acd..47e74b6 100644 (file)
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # ____________________________________________________________
 from time import time
-from copy import copy
 import sys
 import weakref
 from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError
-from tiramisu.setting import owners, multitypes, expires_time, undefined
+from tiramisu.setting import owners, expires_time, undefined
 from tiramisu.autolib import carry_out_calculation
 from tiramisu.i18n import _
 from tiramisu.option import SymLinkOption
@@ -55,36 +54,65 @@ class Values(object):
             raise ConfigError(_('the context does not exist anymore'))
         return context
 
-    def _getdefault(self, opt):
-        """
-        actually retrieves the default value
+    def _getvalue(self, opt, path, is_default, index=undefined):
+        """actually retrieves the value
 
         :param opt: the `option.Option()` object
+        :returns: the option's value (or the default value if not set)
         """
+        setting = self._getcontext().cfgimpl_get_settings()
+        force_default = 'frozen' in setting[opt] and \
+            'force_default_on_freeze' in setting[opt]
+        if not is_default and not force_default:
+            value = self._p_.getvalue(path)
+            if index is not undefined:
+                try:
+                    return value[index]
+                except IndexError:
+                    #value is smaller than expected
+                    #so return default value
+                    pass
+            else:
+                return value
+        #so default value
+        # if value has callback and is not set
+        if opt.impl_has_callback():
+            callback, callback_params = opt._callback
+            if callback_params is None:
+                callback_params = {}
+            value = carry_out_calculation(opt, config=self._getcontext(),
+                                          callback=callback,
+                                          callback_params=callback_params,
+                                          index=index)
+            try:
+                if isinstance(value, list) and index is not undefined:
+                    return value[index]
+                return value
+            except IndexError:
+                pass
         meta = self._getcontext().cfgimpl_get_meta()
         if meta is not None:
+            #FIXME : problème de longueur si meta + slave
+            #doit passer de meta à pas meta
+            #en plus il faut gérer la longueur avec les meta !
+            #FIXME SymlinkOption
             value = meta.cfgimpl_get_values()[opt]
             if isinstance(value, Multi):
-                value = list(value)
-        else:
-            value = opt.impl_getdefault()
-        if opt.impl_is_multi():
-            return copy(value)
-        else:
+                if index is not undefined:
+                    value = value[index]
+                else:
+                    value = list(value)
             return value
-
-    def _getvalue(self, opt, path):
-        """actually retrieves the value
-
-        :param opt: the `option.Option()` object
-        :returns: the option's value (or the default value if not set)
-        """
-        if not self._p_.hasvalue(path):
-            # if there is no value
-            value = self._getdefault(opt)
-        else:
-            # if there is a value
-            value = self._p_.getvalue(path)
+        # now try to get default value
+        value = opt.impl_getdefault()
+        if opt.impl_is_multi() and index is not undefined:
+            if value is None:
+                value = opt.impl_getdefault_multi()
+            else:
+                try:
+                    value = value[index]
+                except IndexError:
+                    value = opt.impl_getdefault_multi()
         return value
 
     def get_modified_values(self):
@@ -121,44 +149,38 @@ class Values(object):
             opt.impl_validate(opt.impl_getdefault(),
                               context, 'validator' in setting)
             context.cfgimpl_reset_cache()
-            if (opt.impl_is_multi() and
-                    opt.impl_get_multitype() == multitypes.master):
-                for slave in opt.impl_get_master_slaves():
-                    self.reset(slave)
+            if opt.impl_is_master_slaves('master'):
+                opt.impl_get_master_slaves().reset(self)
             self._p_.resetvalue(path)
 
     def _isempty(self, opt, value):
         "convenience method to know if an option is empty"
         empty = opt._empty
-        if (not opt.impl_is_multi() and (value is None or value == empty)) or \
-           (opt.impl_is_multi() and (value == [] or
-                                     None in value or empty in value)):
-            return True
-        return False
-
-    def _getcallback_value(self, opt, index=None, max_len=None):
-        """
-        retrieves a value for the options that have a callback
-
-        :param opt: the `option.Option()` object
-        :param index: if an option is multi, only calculates the nth value
-        :type index: int
-        :returns: a calculated value
-        """
-        callback, callback_params = opt._callback
-        if callback_params is None:
-            callback_params = {}
-        return carry_out_calculation(opt, config=self._getcontext(),
-                                     callback=callback,
-                                     callback_params=callback_params,
-                                     index=index, max_len=max_len)
+        if value is not undefined:
+            empty_not_multi = not opt.impl_is_multi() and (value is None or
+                                                           value == empty)
+            empty_multi = opt.impl_is_multi() and (value == [] or
+                                                   None in value or
+                                                   empty in value)
+        else:
+            empty_multi = empty_not_multi = False
+        return empty_not_multi or empty_multi
 
     def __getitem__(self, opt):
         "enables us to use the pythonic dictionary-like access to values"
         return self.getitem(opt)
 
-    def getitem(self, opt, path=None, validate=True, force_permissive=False,
-                force_properties=None, validate_properties=True):
+    def getitem(self, opt, validate=True, force_permissive=False,
+                force_properties=None):
+        """
+        """
+        return self._get_cached_item(opt, validate=validate,
+                                     force_permissive=force_permissive,
+                                     force_properties=force_properties)
+
+    def _get_cached_item(self, opt, path=None, validate=True,
+                         force_permissive=False, force_properties=None,
+                         validate_properties=True):
         if path is None:
             path = self._get_opt_path(opt)
         ntime = None
@@ -170,7 +192,7 @@ class Values(object):
             if is_cached:
                 if opt.impl_is_multi() and not isinstance(value, Multi):
                     #load value so don't need to validate if is not a Multi
-                    value = Multi(value, self.context, opt, path, validate=False)
+                    value = Multi(value, self.context, opt, path)
                 return value
         val = self._getitem(opt, path, validate, force_permissive,
                             force_properties, validate_properties)
@@ -181,85 +203,82 @@ class Values(object):
                     ntime = int(time())
                 ntime = ntime + expires_time
             self._p_.setcache(path, val, ntime)
-
         return val
 
     def _getitem(self, opt, path, validate, force_permissive, force_properties,
                  validate_properties):
-        # options with callbacks
+        if opt.impl_is_master_slaves():
+            return opt.impl_get_master_slaves().getitem(self, opt, path,
+                                                        validate,
+                                                        force_permissive,
+                                                        force_properties,
+                                                        validate_properties)
+        else:
+            return self._get_validated_value(opt, path, validate,
+                                             force_permissive,
+                                             force_properties,
+                                             validate_properties)
+
+    def _get_validated_value(self, opt, path, validate, force_permissive,
+                             force_properties, validate_properties,
+                             index=undefined):
+        """same has getitem but don't touch the cache"""
+        #FIXME expliquer la différence entre index == undefined et index == None
         context = self._getcontext()
         setting = context.cfgimpl_get_settings()
-        is_frozen = 'frozen' in setting[opt]
-        # For calculating properties, we need value (ie for mandatory value).
-        # If value is calculating with a PropertiesOptionError's option
-        # _getcallback_value raise a ConfigError.
-        # We can not raise ConfigError if this option should raise
-        # PropertiesOptionError too. So we get config_error and raise
-        # ConfigError if properties did not raise.
-        config_error = None
-        force_permissives = None
-        # if value has callback and is not set
-        # or frozen with force_default_on_freeze
-        if opt.impl_has_callback() and (
-                self._is_default_owner(opt, path, validate_properties=False) or
-                (is_frozen and 'force_default_on_freeze' in setting[opt])):
-            lenmaster = None
-            no_value_slave = False
-            if (opt.impl_is_multi() and
-                    opt.impl_get_multitype() == multitypes.slave):
-                masterp = self._get_opt_path(opt.impl_get_master_slaves())
-                mastervalue = context.getattr(masterp, validate=False)
-                lenmaster = len(mastervalue)
-                if lenmaster == 0:
-                    value = []
-                    no_value_slave = True
-
-            if not no_value_slave:
-                try:
-                    value = self._getcallback_value(opt, max_len=lenmaster)
-                except ConfigError as err:
-                    # cannot assign config_err directly in python 3.3
-                    config_error = err
-                    value = None
-                    # should not raise PropertiesOptionError if option is
-                    # mandatory
-                    force_permissives = set(['mandatory'])
-                else:
-                    if (opt.impl_is_multi() and
-                            opt.impl_get_multitype() == multitypes.slave):
-                        if not isinstance(value, list):
-                            value = [value for i in range(lenmaster)]
-            if config_error is None:
-                if opt.impl_is_multi():
-                    value = Multi(value, self.context, opt, path, validate)
-        # frozen and force default
-        elif is_frozen and 'force_default_on_freeze' in setting[opt]:
-            value = self._getdefault(opt)
-            if opt.impl_is_multi():
-                value = Multi(value, self.context, opt, path, validate)
-        else:
-            value = self._getvalue(opt, path)
+        is_default = self._is_default_owner(opt, path,
+                                            validate_properties=False,
+                                            validate_meta=False)
+        try:
+            if index is None:
+                gv_index = undefined
+            else:
+                gv_index = index
+            value = self._getvalue(opt, path, is_default, index=gv_index)
+            config_error = None
+        except ConfigError as err:
+            # For calculating properties, we need value (ie for mandatory
+            # value).
+            # If value is calculating with a PropertiesOptionError's option
+            # _getvalue raise a ConfigError.
+            # We can not raise ConfigError if this option should raise
+            # PropertiesOptionError too. So we get config_error and raise
+            # ConfigError if properties did not raise.
+            # cannot assign config_err directly in python 3.3
+            config_error = err
+            # value is not set, for 'undefined' (cannot set None because of
+            # mandatory property)
+            value = undefined
+
+        if config_error is None:
+            if index is undefined:
+                force_index = None
+            else:
+                force_index = index
             if opt.impl_is_multi():
-                # load value so don't need to validate if is not a Multi
-                value = Multi(value, self.context, opt, path, validate=validate)
-        if config_error is None and validate:
-            opt.impl_validate(value, context, 'validator' in setting)
-        if config_error is None and \
-                self._is_default_owner(opt, path, validate_properties=False) and \
-                'force_store_value' in setting[opt]:
-            self.setitem(opt, value, path, is_write=False,
-                         force_permissive=force_permissive)
+                if index is None and not isinstance(value, list):
+                    value = []
+                if force_index is None:
+                    value = Multi(value, self.context, opt, path)
+            if validate:
+                opt.impl_validate(value, context, 'validator' in setting,
+                                  force_index=force_index)
+            #FIXME pas de test avec les metas ...
+            #FIXME et les symlinkoption ...
+            if is_default and 'force_store_value' in setting[opt]:
+                self.setitem(opt, value, path, is_write=False,
+                             force_permissive=force_permissive)
         if validate_properties:
-            setting.validate_properties(opt, False, False, value=value, path=path,
+            setting.validate_properties(opt, False, False, value=value,
+                                        path=path,
                                         force_permissive=force_permissive,
-                                        force_properties=force_properties,
-                                        force_permissives=force_permissives)
+                                        force_properties=force_properties)
         if config_error is not None:
             raise config_error
         return value
 
     def __setitem__(self, opt, value):
-        raise ValueError('you should only set value with config')
+        raise ValueError(_('you should only set value with config'))
 
     def setitem(self, opt, value, path, force_permissive=False,
                 is_write=True):
@@ -270,26 +289,11 @@ class Values(object):
         opt.impl_validate(value, context,
                           'validator' in context.cfgimpl_get_settings())
         if opt.impl_is_multi():
-            value = Multi(value, self.context, opt, path, setitem=True)
-            # Save old value
-            if opt.impl_get_multitype() == multitypes.master and \
-                    self._p_.hasvalue(path):
-                old_value = self._p_.getvalue(path)
-                old_owner = self._p_.getowner(path, None)
-            else:
-                old_value = undefined
-                old_owner = undefined
+            value = Multi(value, self.context, opt, path)
+            if opt.impl_is_master_slaves():
+                opt.impl_get_master_slaves().setitem(self, opt, value, path)
         self._setvalue(opt, path, value, force_permissive=force_permissive,
                        is_write=is_write)
-        if opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.master:
-            try:
-                value._valid_master()
-            except Exception, err:
-                if old_value is not undefined:
-                    self._p_.setvalue(path, old_value, old_owner)
-                else:
-                    self._p_.resetvalue(path)
-                raise err
 
     def _setvalue(self, opt, path, value, force_permissive=False,
                   force_properties=None,
@@ -321,13 +325,15 @@ class Values(object):
         path = self._get_opt_path(opt)
         return self._getowner(opt, path, force_permissive=force_permissive)
 
-    def _getowner(self, opt, path, validate_properties=True, force_permissive=False):
-        meta = self._getcontext().cfgimpl_get_meta()
+    def _getowner(self, opt, path, validate_properties=True,
+                  force_permissive=False, validate_meta=True):
         if validate_properties:
             self._getitem(opt, path, True, force_permissive, None, True)
         owner = self._p_.getowner(path, owners.default)
-        if owner is owners.default and meta is not None:
-            owner = meta.cfgimpl_get_values()._getowner(opt, path)
+        if validate_meta:
+            meta = self._getcontext().cfgimpl_get_meta()
+            if owner is owners.default and meta is not None:
+                owner = meta.cfgimpl_get_values()._getowner(opt, path)
         return owner
 
     def setowner(self, opt, owner):
@@ -349,17 +355,22 @@ class Values(object):
                                 '').format(path, owner))
         self._p_.setowner(path, owner)
 
-    def is_default_owner(self, opt, validate_properties=True):
+    def is_default_owner(self, opt, validate_properties=True,
+                         validate_meta=True):
         """
         :param config: *must* be only the **parent** config
                        (not the toplevel config)
         :return: boolean
         """
         path = self._get_opt_path(opt)
-        return self._is_default_owner(opt, path, validate_properties)
+        return self._is_default_owner(opt, path,
+                                      validate_properties=validate_properties,
+                                      validate_meta=validate_meta)
 
-    def _is_default_owner(self, opt, path, validate_properties=True):
-        return self._getowner(opt, path, validate_properties) == owners.default
+    def _is_default_owner(self, opt, path, validate_properties=True,
+                          validate_meta=True):
+        return self._getowner(opt, path, validate_properties,
+                              validate_meta=validate_meta) == owners.default
 
     def reset_cache(self, only_expired):
         """
@@ -377,7 +388,8 @@ class Values(object):
         :param opt: the `option.Option` object
         :returns: a string with points like "gc.dummy.my_option"
         """
-        return self._getcontext().cfgimpl_get_description().impl_get_path_by_opt(opt)
+        return self._getcontext().cfgimpl_get_description(
+        ).impl_get_path_by_opt(opt)
 
     # information
     def set_information(self, key, value):
@@ -457,13 +469,11 @@ class Multi(list):
     that support item notation for the values of multi options"""
     __slots__ = ('opt', 'path', 'context')
 
-    def __init__(self, value, context, opt, path, validate=True,
-                 setitem=False):
+    def __init__(self, value, context, opt, path):
         """
         :param value: the Multi wraps a list value
         :param context: the home config that has the values
         :param opt: the option object that have this Multi value
-        :param setitem: only if set a value
         """
         if isinstance(value, Multi):
             raise ValueError(_('{0} is already a Multi ').format(opt._name))
@@ -474,11 +484,6 @@ class Multi(list):
         self.context = context
         if not isinstance(value, list):
             value = [value]
-        if validate and self.opt.impl_get_multitype() == multitypes.slave:
-            value = self._valid_slave(value, setitem)
-        elif not setitem and validate and \
-                self.opt.impl_get_multitype() == multitypes.master:
-            self._valid_master()
         super(Multi, self).__init__(value)
 
     def _getcontext(self):
@@ -492,124 +497,73 @@ class Multi(list):
             raise ConfigError(_('the context does not exist anymore'))
         return context
 
-    def _valid_slave(self, value, setitem):
-        #if slave, had values until master's one
-        context = self._getcontext()
-        values = context.cfgimpl_get_values()
-        masterp = context.cfgimpl_get_description().impl_get_path_by_opt(
-            self.opt.impl_get_master_slaves())
-        mastervalue = context.getattr(masterp, validate=False)
-        masterlen = len(mastervalue)
-        valuelen = len(value)
-        if valuelen > masterlen or (valuelen < masterlen and setitem):
-            raise SlaveError(_("invalid len for the slave: {0}"
-                               " which has {1} as master").format(
-                                   self.opt._name, masterp))
-        elif valuelen < masterlen:
-            for num in range(0, masterlen - valuelen):
-                if self.opt.impl_has_callback():
-                    # if callback add a value, but this value will not change
-                    # anymore automaticly (because this value has owner)
-                    index = value.__len__()
-                    value.append(values._getcallback_value(self.opt,
-                                                           index=index))
-                else:
-                    value.append(self.opt.impl_getdefault_multi())
-        #else: same len so do nothing
-        return value
-
-    def _valid_master(self):
-        #masterlen = len(value)
-        values = self._getcontext().cfgimpl_get_values()
-        for slave in self.opt._master_slaves:
-            path = values._get_opt_path(slave)
-            Multi(values._getvalue(slave, path), self.context, slave, path)
-
     def __setitem__(self, index, value):
         self._validate(value, index)
         #assume not checking mandatory property
         super(Multi, self).__setitem__(index, value)
-        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                          self)
 
-    def append(self, value=undefined, force=False):
+    def append(self, value=undefined, force=False, setitem=True):
         """the list value can be updated (appened)
         only if the option is a master
         """
-        context = self._getcontext()
+        values = self._getcontext().cfgimpl_get_values()
         if not force:
-            if self.opt.impl_get_multitype() == multitypes.slave:
+            if self.opt.impl_is_master_slaves('slave'):
                 raise SlaveError(_("cannot append a value on a multi option {0}"
                                    " which is a slave").format(self.opt._name))
-            elif self.opt.impl_get_multitype() == multitypes.master:
-                values = context.cfgimpl_get_values()
-                if value is undefined and self.opt.impl_has_callback():
-                    value = values._getcallback_value(self.opt)
-                    #Force None il return a list
-                    if isinstance(value, list):
-                        value = None
         index = self.__len__()
         if value is undefined:
-            value = self.opt.impl_getdefault_multi()
+            try:
+                value = values._get_validated_value(self.opt, self.path,
+                                                    True, False, None, True,
+                                                    index=index)
+            except IndexError:
+                value = None
         self._validate(value, index)
         super(Multi, self).append(value)
-        context.cfgimpl_get_values()._setvalue(self.opt, self.path,
-                                               self,
-                                               validate_properties=not force)
-        if not force and self.opt.impl_get_multitype() == multitypes.master:
-            for slave in self.opt.impl_get_master_slaves():
-                path = values._get_opt_path(slave)
-                if not values._is_default_owner(slave, path,
-                                                validate_properties=False):
-                    if slave.impl_has_callback():
-                        dvalue = values._getcallback_value(slave, index=index)
-                    else:
-                        dvalue = slave.impl_getdefault_multi()
-                    old_value = values.getitem(slave, path, validate=False,
-                                               validate_properties=False)
-                    if len(old_value) + 1 != self.__len__():
-                        raise SlaveError(_("invalid len for the slave: {0}"
-                                           " which has {1} as master").format(
-                                               self.opt._name, self.__len__()))
-                    values.getitem(slave, path, validate=False,
-                                   validate_properties=False).append(
-                                       dvalue, force=True)
+        if setitem:
+            values._setvalue(self.opt, self.path, self,
+                             validate_properties=not force)
 
     def sort(self, cmp=None, key=None, reverse=False):
-        if self.opt.impl_get_multitype() in [multitypes.slave,
-                                             multitypes.master]:
+        if self.opt.impl_is_master_slaves():
             raise SlaveError(_("cannot sort multi option {0} if master or slave"
                                "").format(self.opt._name))
         if sys.version_info[0] >= 3:
             if cmp is not None:
-                raise ValueError(_('cmp is not permitted in python v3 or greater'))
+                raise ValueError(_('cmp is not permitted in python v3 or '
+                                   'greater'))
             super(Multi, self).sort(key=key, reverse=reverse)
         else:
             super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
-        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                          self)
 
     def reverse(self):
-        if self.opt.impl_get_multitype() in [multitypes.slave,
-                                             multitypes.master]:
+        if self.opt.impl_is_master_slaves():
             raise SlaveError(_("cannot reverse multi option {0} if master or "
                                "slave").format(self.opt._name))
         super(Multi, self).reverse()
-        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                          self)
 
     def insert(self, index, obj):
-        if self.opt.impl_get_multitype() in [multitypes.slave,
-                                             multitypes.master]:
+        if self.opt.impl_is_master_slaves():
             raise SlaveError(_("cannot insert multi option {0} if master or "
                                "slave").format(self.opt._name))
         super(Multi, self).insert(index, obj)
-        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                          self)
 
     def extend(self, iterable):
-        if self.opt.impl_get_multitype() in [multitypes.slave,
-                                             multitypes.master]:
+        if self.opt.impl_is_master_slaves():
             raise SlaveError(_("cannot extend multi option {0} if master or "
                                "slave").format(self.opt._name))
         super(Multi, self).extend(iterable)
-        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+        self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path,
+                                                          self)
 
     def _validate(self, value, force_index):
         if value is not None:
@@ -634,18 +588,14 @@ class Multi(list):
         """
         context = self._getcontext()
         if not force:
-            if self.opt.impl_get_multitype() == multitypes.slave:
+            if self.opt.impl_is_master_slaves('slave'):
                 raise SlaveError(_("cannot pop a value on a multi option {0}"
                                    " which is a slave").format(self.opt._name))
-            if self.opt.impl_get_multitype() == multitypes.master:
-                for slave in self.opt.impl_get_master_slaves():
-                    values = context.cfgimpl_get_values()
-                    if not values.is_default_owner(slave, validate_properties=False):
-                        #get multi without valid properties
-                        values.getitem(slave, validate=False,
-                                       validate_properties=False
-                                       ).pop(index, force=True)
+            if self.opt.impl_is_master_slaves('master'):
+                self.opt.impl_get_master_slaves().pop(
+                    context.cfgimpl_get_values(), index)
         #set value without valid properties
         ret = super(Multi, self).pop(index)
-        context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
+        context.cfgimpl_get_values()._setvalue(self.opt, self.path, self,
+                                               validate_properties=not force)
         return ret