optimisations and all is properties
[tiramisu.git] / tiramisu / option.py
index 9ed8aed..3710d6d 100644 (file)
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
 import re
+from copy import copy
 from types import FunctionType
-from tiramisu.basetype import BaseType
-from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError,
-    RequiresError, RequirementRecursionError, MandatoryError,
-    PropertiesOptionError)
+from tiramisu.error import (ConfigError, NotFoundError, ConflictConfigError,
+                            RequiresError, RequirementRecursionError,
+                            PropertiesOptionError)
 from tiramisu.autolib import carry_out_calculation
-from tiramisu.setting import groups, owners
+from tiramisu.setting import groups, multitypes
 
-requires_actions = [('hide', 'show'), ('enable', 'disable'),
-                    ('freeze', 'unfreeze')]
-
-available_actions = []
-reverse_actions = {}
-for act1, act2 in requires_actions:
-    available_actions.extend([act1, act2])
-    reverse_actions[act1] = act2
-    reverse_actions[act2] = act1
-# ____________________________________________________________
 name_regexp = re.compile(r'^\d+')
 
+
 def valid_name(name):
     try:
         name = str(name)
@@ -53,7 +44,9 @@ def valid_name(name):
 #____________________________________________________________
 #
 
-class BaseInformation:
+
+class BaseInformation(object):
+    __slots__ = ('informations')
 
     def set_information(self, key, value):
         """updates the information's attribute
@@ -64,29 +57,33 @@ class BaseInformation:
         """
         self.informations[key] = value
 
-    def get_information(self, key):
+    def get_information(self, key, default=None):
         """retrieves one information's item
 
         :param key: the item string (ex: "help")
         """
         if key in self.informations:
             return self.informations[key]
+        elif default is not None:
+            return default
         else:
             raise ValueError("Information's item not found: {0}".format(key))
 
-class Option(BaseType, BaseInformation):
+
+class Option(BaseInformation):
     """
     Abstract base class for configuration option's.
 
     Reminder: an Option object is **not** a container for the value
     """
-    #freeze means: cannot modify the value of an Option once set
-    _frozen = False
-    #if an Option has been frozen, shall return the default value
-    _force_default_on_freeze = False
+    __slots__ = ('_name', '_requires', 'multi', '_validator', 'default_multi',
+                 'default', '_properties', 'callback', 'multitype',
+                 'master_slaves')
+
     def __init__(self, name, doc, default=None, default_multi=None,
-                 requires=None, mandatory=False, multi=False, callback=None,
-                 callback_params=None, validator=None, validator_args={}):
+                 requires=None, multi=False, callback=None,
+                 callback_params=None, validator=None, validator_args=None,
+                 properties=None):
         """
         :param name: the option's name
         :param doc: the option's description
@@ -107,51 +104,58 @@ class Option(BaseType, BaseInformation):
         if not valid_name(name):
             raise NameError("invalid name: {0} for option".format(name))
         self._name = name
-        self.doc = doc
+        self.informations = {}
+        self.set_information('doc', doc)
+        validate_requires_arg(requires, self._name)
         self._requires = requires
-        self._mandatory = mandatory
         self.multi = multi
-        self._validator = None
-        self._validator_args = None
+        #self._validator_args = None
         if validator is not None:
             if type(validator) != FunctionType:
                 raise TypeError("validator must be a function")
-            self._validator = validator
-            if validator_args is not None:
-                self._validator_args = validator_args
+            self._validator = (validator, validator_args)
+        else:
+            self._validator = None
         if not self.multi and default_multi is not None:
             raise ConfigError("a default_multi is set whereas multi is False"
-                  " in option: {0}".format(name))
+                              " in option: {0}".format(name))
         if default_multi is not None and not self._validate(default_multi):
             raise ConfigError("invalid default_multi value {0} "
-                "for option {1}".format(str(default_multi), name))
-        self.default_multi = default_multi
-        #if self.multi and default_multi is None:
-        #    _cfgimpl_warnings[name] = DefaultMultiWarning
+                              "for option {1}".format(str(default_multi), name))
         if callback is not None and (default is not None or default_multi is not None):
             raise ConfigError("defaut values not allowed if option: {0} "
-                "is calculated".format(name))
-        self.callback = callback
-        if self.callback is None and callback_params is not None:
-            raise ConfigError("params defined for a callback function but"
-            " no callback defined yet for option {0}".format(name))
-        self.callback_params = callback_params
-        if self.multi == True:
-            if default == None:
+                              "is calculated".format(name))
+        if callback is None and callback_params is not None:
+            raise ConfigError("params defined for a callback function but "
+                              "no callback defined yet for option {0}".format(name))
+        if callback is not None:
+            self.callback = (callback, callback_params)
+        else:
+            self.callback = None
+        if self.multi:
+            if default is None:
                 default = []
             if not isinstance(default, list):
                 raise ConfigError("invalid default value {0} "
-                "for option {1} : not list type".format(str(default), name))
+                                  "for option {1} : not list type"
+                                  "".format(str(default), name))
             if not self.validate(default, False):
                 raise ConfigError("invalid default value {0} "
-                "for option {1}".format(str(default), name))
+                                  "for option {1}"
+                                  "".format(str(default), name))
+            self.multitype = multitypes.default
+            self.default_multi = default_multi
         else:
-            if default != None and not self.validate(default, False):
+            if default is not None and not self.validate(default, False):
                 raise ConfigError("invalid default value {0} "
-                                         "for option {1}".format(str(default), name))
+                                  "for option {1}".format(str(default), name))
         self.default = default
-        self.properties = [] # 'hidden', 'disabled'...
-        self.informations = {}
+        if properties is None:
+            properties = ()
+        if not isinstance(properties, tuple):
+            raise ConfigError('invalid properties type {0} for {1},'
+                              ' must be a tuple'.format(type(properties), self._name))
+        self._properties = properties  # 'hidden', 'disabled'...
 
     def validate(self, value, validate=True):
         """
@@ -159,25 +163,25 @@ class Option(BaseType, BaseInformation):
         :param validate: if true enables ``self._validator`` validation
         """
         # generic calculation
-        if self.multi == False:
+        if not self.multi:
             # None allows the reset of the value
-            if value != None:
+            if value is not None:
                 # customizing the validator
                 if validate and self._validator is not None and \
-                        not self._validator(value, **self._validator_args):
+                        not self._validator[0](value, **self._validator[1]):
                     return False
                 return self._validate(value)
         else:
             if not isinstance(value, list):
                 raise ConfigError("invalid value {0} "
-                        "for option {1} which must be a list".format(value,
-                        self._name))
+                                  "for option {1} which must be a list"
+                                  "".format(value, self._name))
             for val in value:
                 # None allows the reset of the value
-                if val != None:
+                if val is not None:
                     # customizing the validator
                     if validate and self._validator is not None and \
-                            not self._validator(val, **self._validator_args):
+                            not self._validator[0](val, **self._validator[1]):
                         return False
                     if not self._validate(val):
                         return False
@@ -185,7 +189,7 @@ class Option(BaseType, BaseInformation):
 
     def getdefault(self, default_multi=False):
         "accessing the default value"
-        if default_multi == False or not self.is_multi():
+        if not default_multi or not self.is_multi():
             return self.default
         else:
             return self.getdefault_multi()
@@ -196,148 +200,88 @@ class Option(BaseType, BaseInformation):
 
     def is_empty_by_default(self):
         "no default value has been set yet"
-        if ((not self.is_multi() and self.default == None) or
+        if ((not self.is_multi() and self.default is None) or
                 (self.is_multi() and (self.default == [] or None in self.default))):
             return True
         return False
 
-    def force_default(self):
-        "if an Option has been frozen, shall return the default value"
-        self._force_default_on_freeze = True
-
-    def hascallback_and_isfrozen():
-        return self._frozen and self.has_callback()
-
-    def is_forced_on_freeze(self):
-        "if an Option has been frozen, shall return the default value"
-        return self._frozen and self._force_default_on_freeze
-
     def getdoc(self):
         "accesses the Option's doc"
-        return self.doc
-
-    def getcallback(self):
-        "a callback is only a link, the name of an external hook"
-        return self.callback
+        return self.get_information('doc')
 
     def has_callback(self):
         "to know if a callback has been defined or not"
-        if self.callback == None:
+        if self.callback is None:
             return False
         else:
             return True
 
     def getcallback_value(self, config):
-        return carry_out_calculation(self._name,
-                option=self, config=config)
-
-    def getcallback_params(self):
-        "if a callback has been defined, returns his arity"
-        return self.callback_params
-
-    def setowner(self, config, owner):
-        """
-        :param config: *must* be only the **parent** config
-                       (not the toplevel config)
-        :param owner: is a **real** owner, that is an object
-                      that lives in setting.owners
-        """
-        name = self._name
-        if not isinstance(owner, owners.Owner):
-            raise ConfigError("invalid type owner for option: {0}".format(
-                    str(name)))
-        config._cfgimpl_context._cfgimpl_values.owners[self] = owner
-
-    def getowner(self, config):
-        "config *must* be only the **parent** config (not the toplevel config)"
-        return config._cfgimpl_context._cfgimpl_values.getowner(self)
-
-    def get_previous_value(self, config):
-        return config._cfgimpl_context._cfgimpl_values.get_previous_value(self)
+        callback, callback_params = self.callback
+        if callback_params is None:
+            callback_params = {}
+        return carry_out_calculation(self._name, config=config,
+                                     callback=callback,
+                                     callback_params=callback_params)
 
     def reset(self, config):
         """resets the default value and owner
         """
         config._cfgimpl_context._cfgimpl_values.reset(self)
 
-    def is_default_owner(self, config):
-        """
-        :param config: *must* be only the **parent** config
-                       (not the toplevel config)
-        :return: boolean
-        """
-        return self.getowner(config) == owners.default
-
     def setoption(self, config, value):
         """changes the option's value with the value_owner's who
         :param config: the parent config is necessary here to store the value
         """
         name = self._name
-        rootconfig = config._cfgimpl_get_toplevel()
-        if not self.validate(value,
-                        config._cfgimpl_context._cfgimpl_settings.validator):
+        setting = config.cfgimpl_get_settings()
+        if not self.validate(value, setting.has_property('validator')):
             raise ConfigError('invalid value %s for option %s' % (value, name))
-        if self.is_mandatory():
-            # value shall not be '' for a mandatory option
-            # so '' is considered as being None
-            if not self.is_multi() and value == '':
-                value = None
-#            if self.is_multi() and '' in value:
-#                value = Multi([{'': None}.get(i, i) for i in value],
-#                                config._cfgimpl_context, self)
-            if config._cfgimpl_context._cfgimpl_settings.is_mandatory() \
-                and ((self.is_multi() and value == []) or \
-                (not self.is_multi() and value is None)):
-                raise MandatoryError('cannot change the value to %s for '
-              'option %s' % (value, name))
-        if self not in config._cfgimpl_descr._children:
+        if self not in config._cfgimpl_descr._children[1]:
             raise AttributeError('unknown option %s' % (name))
 
-        if config._cfgimpl_context._cfgimpl_settings.is_frozen_for_everything():
+        if setting.has_property('everything_frozen'):
             raise TypeError("cannot set a value to the option {} if the whole "
-            "config has been frozen".format(name))
+                            "config has been frozen".format(name))
 
-        if config._cfgimpl_context._cfgimpl_settings.is_frozen() \
-                                                        and self.is_frozen():
+        if setting.has_property('frozen') and setting.has_property('frozen',
+                                                                   self):
             raise TypeError('cannot change the value to %s for '
-               'option %s this option is frozen' % (str(value), name))
+                            'option %s this option is frozen' % (str(value), name))
         apply_requires(self, config)
-        config._cfgimpl_context._cfgimpl_values[self] = value
+        config.cfgimpl_get_values()[self] = value
 
     def getkey(self, value):
         return value
-    # ____________________________________________________________
-    "freeze utility"
-    def freeze(self):
-        self._frozen = True
-        return True
-    def unfreeze(self):
-        self._frozen = False
-    def is_frozen(self):
-        return self._frozen
-    # ____________________________________________________________
+
     def is_multi(self):
         return self.multi
-    def is_mandatory(self):
-        return self._mandatory
+
 
 class ChoiceOption(Option):
+    __slots__ = ('values', 'open_values', 'opt_type')
     opt_type = 'string'
 
     def __init__(self, name, doc, values, default=None, default_multi=None,
-                 requires=None, mandatory=False, multi=False, callback=None,
+                 requires=None, multi=False, callback=None,
                  callback_params=None, open_values=False, validator=None,
-                 validator_args={}):
+                 validator_args=None, properties=()):
+        if not isinstance(values, tuple):
+            raise ConfigError('values must be a tuple for {0}'.format(name))
         self.values = values
-        if open_values not in [True, False]:
+        if open_values not in (True, False):
             raise ConfigError('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, mandatory=mandatory, validator=validator,
-                        validator_args=validator_args)
+                                           default_multi=default_multi,
+                                           callback=callback,
+                                           callback_params=callback_params,
+                                           requires=requires,
+                                           multi=multi,
+                                           validator=validator,
+                                           validator_args=validator_args,
+                                           properties=properties)
 
     def _validate(self, value):
         if not self.open_values:
@@ -345,72 +289,91 @@ class ChoiceOption(Option):
         else:
             return True
 
+
 class BoolOption(Option):
+    __slots__ = ('opt_type')
     opt_type = 'bool'
 
     def _validate(self, value):
         return isinstance(value, bool)
 
+
 class IntOption(Option):
+    __slots__ = ('opt_type')
     opt_type = 'int'
 
     def _validate(self, value):
         return isinstance(value, int)
 
+
 class FloatOption(Option):
+    __slots__ = ('opt_type')
     opt_type = 'float'
 
     def _validate(self, value):
         return isinstance(value, float)
 
+
 class StrOption(Option):
+    __slots__ = ('opt_type')
     opt_type = 'string'
 
     def _validate(self, value):
         return isinstance(value, str)
 
+
 class UnicodeOption(Option):
+    __slots__ = ('opt_type')
     opt_type = 'unicode'
 
     def _validate(self, value):
         return isinstance(value, unicode)
 
+
 class SymLinkOption(object):
+    __slots__ = ('_name', 'opt')
     opt_type = 'symlink'
 
     def __init__(self, name, path, opt):
         self._name = name
-        self.path = path
         self.opt = opt
 
     def setoption(self, config, value):
-        setattr(config._cfgimpl_get_toplevel(), self.path, value)
+        context = config.cfgimpl_get_context()
+        path = context.cfgimpl_get_description().get_path_by_opt(self.opt)
+        setattr(context, path, value)
 
     def __getattr__(self, name):
-        if name in ('_name', 'path', 'opt', 'setoption'):
-            return self.__dict__[name]
+        if name in ('_name', 'opt', 'setoption'):
+            return object.__gettattr__(self, name)
         else:
             return getattr(self.opt, name)
 
+
 class IPOption(Option):
+    __slots__ = ('opt_type')
     opt_type = 'ip'
 
     def _validate(self, value):
         # by now the validation is nothing but a string, use IPy instead
         return isinstance(value, str)
 
+
 class NetmaskOption(Option):
+    __slots__ = ('opt_type')
     opt_type = 'netmask'
 
     def _validate(self, value):
         # by now the validation is nothing but a string, use IPy instead
         return isinstance(value, str)
 
-class OptionDescription(BaseType, BaseInformation):
+
+class OptionDescription(BaseInformation):
     """Config's schema (organisation, group) and container of Options"""
-    # the group_type is useful for filtering OptionDescriptions in a config
-    group_type = groups.default
-    def __init__(self, name, doc, children, requires=None):
+    __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
+                 '_properties', '_children')
+
+    def __init__(self, name, doc, children, requires=None, properties=()):
         """
         :param children: is a list of option descriptions (including
         ``OptionDescription`` instances for nested namespaces).
@@ -418,49 +381,55 @@ class OptionDescription(BaseType, BaseInformation):
         if not valid_name(name):
             raise NameError("invalid name: {0} for option descr".format(name))
         self._name = name
-        self.doc = doc
-        self._children = children
-        self._requires = requires
-        self._build()
-        self.properties = [] # 'hidden', 'disabled'...
         self.informations = {}
-        self._cache_paths = {}
+        self.set_information('doc', doc)
+        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 ConflictConfigError('duplicate option name: '
+                                          '{0}'.format(child))
+            old = child
+        self._children = (tuple(child_names), tuple(children))
+        validate_requires_arg(requires, self._name)
+        self._requires = requires
+        self._cache_paths = None
+        if not isinstance(properties, tuple):
+            raise ConfigError('invalid properties type {0} for {1},'
+                              ' must be a tuple'.format(type(properties), self._name))
+        self._properties = properties  # 'hidden', 'disabled'...
+        # the group_type is useful for filtering OptionDescriptions in a config
+        self._group_type = groups.default
 
     def getdoc(self):
-        return self.doc
-
-    def _build(self):
-        for child in self._children:
-            setattr(self, child._name, child)
-
-    def add_child(self, child):
-        "dynamically adds a configuration option"
-        #Nothing is static. Even the Mona Lisa is falling apart.
-        for ch in self._children:
-            if isinstance(ch, Option):
-                if child._name == ch._name:
-                    raise ConflictConfigError("existing option : {0}".format(
-                                                                   child._name))
-        self._children.append(child)
-        setattr(self, child._name, child)
-
-    def update_child(self, child):
-        "modification of an existing option"
-        # XXX : corresponds to the `redefine`, is it usefull
-        pass
+        return self.get_information('doc')
+
+    def __getattr__(self, name):
+        if name in self._children[0]:
+            return self._children[1][self._children[0].index(name)]
+        else:
+            try:
+                object.__getattr__(self, name)
+            except AttributeError:
+                raise AttributeError('unknown Option {} in OptionDescription {}'
+                                     ''.format(name, self._name))
 
     def getkey(self, config):
         return tuple([child.getkey(getattr(config, child._name))
-                      for child in self._children])
+                      for child in self._children[1]])
 
     def getpaths(self, include_groups=False, currpath=None):
         """returns a list of all paths in self, recursively
            currpath should not be provided (helps with recursion)
         """
+        #FIXME : cache
         if currpath is None:
             currpath = []
         paths = []
-        for option in self._children:
+        for option in self._children[1]:
             attr = option._name
             if attr.startswith('_cfgimpl'):
                 continue
@@ -469,29 +438,57 @@ class OptionDescription(BaseType, BaseInformation):
                     paths.append('.'.join(currpath + [attr]))
                 currpath.append(attr)
                 paths += option.getpaths(include_groups=include_groups,
-                                        currpath=currpath)
+                                         currpath=currpath)
                 currpath.pop()
             else:
                 paths.append('.'.join(currpath + [attr]))
         return paths
 
-    def build_cache(self, cache=None, currpath=None):
-        if currpath is None and self._cache_paths != {}:
+    def build_cache(self, cache_path=None, cache_option=None, currpath=None):
+        if currpath is None and self._cache_paths is not None:
             return
         if currpath is None:
+            save = True
             currpath = []
-        if cache is None:
-            cache = self._cache_paths
-        for option in self._children:
+        else:
+            save = False
+        if cache_path is None:
+            cache_path = []
+            cache_option = []
+        for option in self._children[1]:
             attr = option._name
             if attr.startswith('_cfgimpl'):
                 continue
+            cache_option.append(option)
+            cache_path.append(str('.'.join(currpath + [attr])))
             if isinstance(option, OptionDescription):
                 currpath.append(attr)
-                option.build_cache(cache, currpath)
+                option.build_cache(cache_path, cache_option, currpath)
                 currpath.pop()
-            else:
-                cache[option] = str('.'.join(currpath + [attr]))
+        if save:
+            #valid no duplicated option
+            valid_child = copy(cache_option)
+            valid_child.sort()
+            old = None
+            for child in valid_child:
+                if child == old:
+                    raise ConflictConfigError('duplicate option: '
+                                              '{0}'.format(child))
+                old = child
+            self._cache_paths = (tuple(cache_option), tuple(cache_path))
+
+    def get_opt_by_path(self, path):
+        try:
+            return self._cache_paths[0][self._cache_paths[1].index(path)]
+        except ValueError:
+            raise NotFoundError('no option for path {}'.format(path))
+
+    def get_path_by_opt(self, opt):
+        try:
+            return self._cache_paths[1][self._cache_paths[0].index(opt)]
+        except ValueError:
+            raise NotFoundError('no option {} found'.format(opt))
+
     # ____________________________________________________________
     def set_group_type(self, group_type):
         """sets a given group object to an OptionDescription
@@ -499,115 +496,123 @@ class OptionDescription(BaseType, BaseInformation):
         :param group_type: an instance of `GroupType` or `MasterGroupType`
                               that lives in `setting.groups`
         """
+        if self._group_type != groups.default:
+            ConfigError('cannot change group_type if already set '
+                        '(old {}, new {})'.format(self._group_type, group_type))
         if isinstance(group_type, groups.GroupType):
-            self.group_type = group_type
+            self._group_type = group_type
             if isinstance(group_type, groups.MasterGroupType):
+                #if master (same name has group) is set
                 identical_master_child_name = False
-                for child in self._children:
+                #for collect all slaves
+                slaves = []
+                master = None
+                for child in self._children[1]:
                     if isinstance(child, OptionDescription):
                         raise ConfigError("master group {} shall not have "
-                            "a subgroup".format(self._name))
+                                          "a subgroup".format(self._name))
                     if not child.multi:
                         raise ConfigError("not allowed option {0} in group {1}"
-                            ": this option is not a multi".format(child._name,
-                            self._name))
+                                          ": this option is not a multi"
+                                          "".format(child._name, self._name))
                     if child._name == self._name:
                         identical_master_child_name = True
+                        child.multitype = multitypes.master
+                        master = child
+                    else:
+                        slaves.append(child)
+                if master is None:
+                    raise ConfigError('master group with wrong master name for {}'
+                                      ''.format(self._name))
+                master.master_slaves = tuple(slaves)
+                for child in self._children[1]:
+                    if child != master:
+                        child.master_slaves = master
+                        child.multitype = multitypes.slave
                 if not identical_master_child_name:
                     raise ConfigError("the master group: {} has not any "
-                     "master child".format(self._name))
+                                      "master child".format(self._name))
         else:
             raise ConfigError('not allowed group_type : {0}'.format(group_type))
 
     def get_group_type(self):
-        return self.group_type
-    # ____________________________________________________________
-    "actions API"
-    def hide(self):
-        super(OptionDescription, self).hide()
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.hide()
-    def show(self):
-        super(OptionDescription, self).show()
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.show()
-
-    def disable(self):
-        super(OptionDescription, self).disable()
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.disable()
-    def enable(self):
-        super(OptionDescription, self).enable()
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.enable()
-# ____________________________________________________________
+        return self._group_type
+
 
 def validate_requires_arg(requires, name):
-    "malformed requirements"
-    config_action = []
-    for req in requires:
-        if not type(req) == tuple and len(req) != 3:
-            raise RequiresError("malformed requirements for option:"
-                                           " {0}".format(name))
-        action = req[2]
-        if action not in available_actions:
-            raise RequiresError("malformed requirements for option: {0}"
-                                " unknown action: {1}".format(name, action))
-        if reverse_actions[action] in config_action:
-            raise RequiresError("inconsistency in action types for option: {0}"
-                                " action: {1} in contradiction with {2}\n"
-                                " ({3})".format(name, action,
-                                    reverse_actions[action], requires))
-        config_action.append(action)
-
-def build_actions(requires):
-    "action are hide, show, enable, disable..."
-    trigger_actions = {}
-    for require in requires:
-        action = require[2]
-        trigger_actions.setdefault(action, []).append(require)
-    return trigger_actions
-
-def apply_requires(opt, config, permissive=False):
+    "check malformed requirements"
+    if requires is not None:
+        config_action = {}
+        for req in requires:
+            if not type(req) == tuple:
+                raise RequiresError("malformed requirements type for option:"
+                                    " {0}, must be a tuple".format(name))
+            if len(req) == 3:
+                action = req[2]
+                inverse = False
+            elif len(req) == 4:
+                action = req[2]
+                inverse = req[3]
+            else:
+                raise RequiresError("malformed requirements for option: {0}"
+                                    " invalid len".format(name))
+            if action in config_action:
+                if inverse != config_action[action]:
+                    raise RequiresError("inconsistency in action types for option: {0}"
+                                        " action: {1}".format(name, action))
+            else:
+                config_action[action] = inverse
+
+
+def apply_requires(opt, config):
     "carries out the jit (just in time requirements between options"
+    def build_actions(requires):
+        "action are hide, show, enable, disable..."
+        trigger_actions = {}
+        for require in requires:
+            action = require[2]
+            trigger_actions.setdefault(action, []).append(require)
+        return trigger_actions
+    #for symlink
     if hasattr(opt, '_requires') and opt._requires is not None:
-        rootconfig = config._cfgimpl_get_toplevel()
-        validate_requires_arg(opt._requires, opt._name)
         # filters the callbacks
+        setting = config.cfgimpl_get_settings()
         trigger_actions = build_actions(opt._requires)
+        if isinstance(opt, OptionDescription):
+            optpath = config._cfgimpl_get_path() + '.' + opt._name
+        else:
+            optpath = config.cfgimpl_get_context().cfgimpl_get_description().get_path_by_opt(opt)
         for requires in trigger_actions.values():
             matches = False
             for require in requires:
-                name, expected, action = require
-                path = config._cfgimpl_get_path() + '.' + opt._name
-                if name.startswith(path):
+                if len(require) == 3:
+                    path, expected, action = require
+                    inverse = False
+                elif len(require) == 4:
+                    path, expected, action, inverse = require
+                if path.startswith(optpath):
                     raise RequirementRecursionError("malformed requirements "
-                          "imbrication detected for option: '{0}' "
-                          "with requirement on: '{1}'".format(path, name))
-                homeconfig, shortname = rootconfig.cfgimpl_get_home_by_path(name)
+                                                    "imbrication detected for option: '{0}' "
+                                                    "with requirement on: '{1}'".format(optpath, path))
                 try:
-                    value = homeconfig._getattr(shortname, permissive=True)
+                    value = config.cfgimpl_get_context()._getattr(path, force_permissive=True)
                 except PropertiesOptionError, err:
                     properties = err.proptype
-                    if permissive:
-                        for perm in \
-                                config._cfgimpl_context._cfgimpl_settings.permissive:
-                            if perm in properties:
-                                properties.remove(perm)
-                    if properties != []:
-                        raise NotFoundError("option '{0}' has requirement's property error: "
-                                     "{1} {2}".format(opt._name, name, properties))
+                    raise NotFoundError("option '{0}' has requirement's property error: "
+                                        "{1} {2}".format(opt._name, path, properties))
                 except Exception, err:
                     raise NotFoundError("required option not found: "
-                                                             "{0}".format(name))
+                                        "{0}".format(path))
                 if value == expected:
-                    getattr(opt, action)() #.hide() or show() or...
-                    # FIXME generic programming opt.property_launch(action, False)
+                    if inverse:
+                        setting.del_property(action, opt)
+                    else:
+                        setting.add_property(action, opt)
                     matches = True
-            # no callback has been triggered, then just reverse the action
+                    #FIXME optimisation : fait un double break non ? voire un return
+            # no requirement has been triggered, then just reverse the action
             if not matches:
-                getattr(opt, reverse_actions[action])()
+                if inverse:
+                    setting.add_property(action, opt)
+                else:
+                    setting.del_property(action, opt)