rename FileOption to FilenameOption
[tiramisu.git] / tiramisu / option.py
1 # -*- coding: utf-8 -*-
2 "option types and option description"
3 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
23 import re
24 import sys
25 from copy import copy, deepcopy
26 from types import FunctionType
27 from IPy import IP
28 import warnings
29
30 from tiramisu.error import ConfigError, ConflictError, ValueWarning
31 from tiramisu.setting import groups, multitypes
32 from tiramisu.i18n import _
33 from tiramisu.autolib import carry_out_calculation
34
35 name_regexp = re.compile(r'^\d+')
36 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
37                    'make_dict', 'unwrap_from_path', 'read_only',
38                    'read_write', 'getowner', 'set_contexts')
39
40
41 def valid_name(name):
42     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
43     try:
44         name = str(name)
45     except:
46         return False
47     if re.match(name_regexp, name) is None and not name.startswith('_') \
48             and name not in forbidden_names \
49             and not name.startswith('impl_') \
50             and not name.startswith('cfgimpl_'):
51         return True
52     else:
53         return False
54 #____________________________________________________________
55 #
56
57
58 class BaseOption(object):
59     """This abstract base class stands for attribute access
60     in options that have to be set only once, it is of course done in the
61     __setattr__ method
62     """
63     __slots__ = ('_name', '_requires', '_properties', '_readonly',
64                  '_calc_properties', '_impl_informations',
65                  '_state_readonly', '_state_requires', '_stated')
66
67     def __init__(self, name, doc, requires, properties):
68         if not valid_name(name):
69             raise ValueError(_("invalid name: {0} for option").format(name))
70         self._name = name
71         self._impl_informations = {}
72         self.impl_set_information('doc', doc)
73         self._calc_properties, self._requires = validate_requires_arg(
74             requires, self._name)
75         if properties is None:
76             properties = tuple()
77         if not isinstance(properties, tuple):
78             raise TypeError(_('invalid properties type {0} for {1},'
79                             ' must be a tuple').format(
80                                 type(properties),
81                                 self._name))
82         if self._calc_properties is not None and properties is not tuple():
83             set_forbidden_properties = set(properties) & self._calc_properties
84             if set_forbidden_properties != frozenset():
85                 raise ValueError('conflict: properties already set in '
86                                  'requirement {0}'.format(
87                                      list(set_forbidden_properties)))
88         self._properties = properties  # 'hidden', 'disabled'...
89
90     def __setattr__(self, name, value):
91         """set once and only once some attributes in the option,
92         like `_name`. `_name` cannot be changed one the option and
93         pushed in the :class:`tiramisu.option.OptionDescription`.
94
95         if the attribute `_readonly` is set to `True`, the option is
96         "frozen" (which has noting to do with the high level "freeze"
97         propertie or "read_only" property)
98         """
99         if not name.startswith('_state') and not name.startswith('_cache'):
100             is_readonly = False
101             # never change _name
102             if name == '_name':
103                 try:
104                     self._name
105                     #so _name is already set
106                     is_readonly = True
107                 except:
108                     pass
109             elif name != '_readonly':
110                 try:
111                     if self._readonly is True:
112                         is_readonly = True
113                 except AttributeError:
114                     self._readonly = False
115             if is_readonly:
116                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
117                                        " read-only").format(
118                                            self.__class__.__name__,
119                                            self._name,
120                                            name))
121         object.__setattr__(self, name, value)
122
123     # information
124     def impl_set_information(self, key, value):
125         """updates the information's attribute
126         (which is a dictionary)
127
128         :param key: information's key (ex: "help", "doc"
129         :param value: information's value (ex: "the help string")
130         """
131         self._impl_informations[key] = value
132
133     def impl_get_information(self, key, default=None):
134         """retrieves one information's item
135
136         :param key: the item string (ex: "help")
137         """
138         if key in self._impl_informations:
139             return self._impl_informations[key]
140         elif default is not None:
141             return default
142         else:
143             raise ValueError(_("information's item not found: {0}").format(
144                 key))
145
146     def _impl_convert_requires(self, descr, load=False):
147         """export of the requires during the serialization process
148
149         :type descr: :class:`tiramisu.option.OptionDescription`
150         :param load: `True` if we are at the init of the option description
151         :type load: bool
152         """
153         if not load and self._requires is None:
154             self._state_requires = None
155         elif load and self._state_requires is None:
156             self._requires = None
157             del(self._state_requires)
158         else:
159             if load:
160                 _requires = self._state_requires
161             else:
162                 _requires = self._requires
163             new_value = []
164             for requires in _requires:
165                 new_requires = []
166                 for require in requires:
167                     if load:
168                         new_require = [descr.impl_get_opt_by_path(require[0])]
169                     else:
170                         new_require = [descr.impl_get_path_by_opt(require[0])]
171                     new_require.extend(require[1:])
172                     new_requires.append(tuple(new_require))
173                 new_value.append(tuple(new_requires))
174             if load:
175                 del(self._state_requires)
176                 self._requires = new_value
177             else:
178                 self._state_requires = new_value
179
180     # serialize
181     def _impl_getstate(self, descr):
182         """the under the hood stuff that need to be done
183         before the serialization.
184
185         :param descr: the parent :class:`tiramisu.option.OptionDescription`
186         """
187         self._stated = True
188         for func in dir(self):
189             if func.startswith('_impl_convert_'):
190                 getattr(self, func)(descr)
191         self._state_readonly = self._readonly
192
193     def __getstate__(self, stated=True):
194         """special method to enable the serialization with pickle
195         Usualy, a `__getstate__` method does'nt need any parameter,
196         but somme under the hood stuff need to be done before this action
197
198         :parameter stated: if stated is `True`, the serialization protocol
199                            can be performed, not ready yet otherwise
200         :parameter type: bool
201         """
202         try:
203             self._stated
204         except AttributeError:
205             raise SystemError(_('cannot serialize Option, '
206                                 'only in OptionDescription'))
207         slots = set()
208         for subclass in self.__class__.__mro__:
209             if subclass is not object:
210                 slots.update(subclass.__slots__)
211         slots -= frozenset(['_cache_paths', '_cache_consistencies',
212                             '__weakref__'])
213         states = {}
214         for slot in slots:
215             # remove variable if save variable converted
216             # in _state_xxxx variable
217             if '_state' + slot not in slots:
218                 if slot.startswith('_state'):
219                     # should exists
220                     states[slot] = getattr(self, slot)
221                     # remove _state_xxx variable
222                     self.__delattr__(slot)
223                 else:
224                     try:
225                         states[slot] = getattr(self, slot)
226                     except AttributeError:
227                         pass
228         if not stated:
229             del(states['_stated'])
230         return states
231
232     # unserialize
233     def _impl_setstate(self, descr):
234         """the under the hood stuff that need to be done
235         before the serialization.
236
237         :type descr: :class:`tiramisu.option.OptionDescription`
238         """
239         for func in dir(self):
240             if func.startswith('_impl_convert_'):
241                 getattr(self, func)(descr, load=True)
242         try:
243             self._readonly = self._state_readonly
244             del(self._state_readonly)
245             del(self._stated)
246         except AttributeError:
247             pass
248
249     def __setstate__(self, state):
250         """special method that enables us to serialize (pickle)
251
252         Usualy, a `__setstate__` method does'nt need any parameter,
253         but somme under the hood stuff need to be done before this action
254
255         :parameter state: a dict is passed to the loads, it is the attributes
256                           of the options object
257         :type state: dict
258         """
259         for key, value in state.items():
260             setattr(self, key, value)
261
262
263 class Option(BaseOption):
264     """
265     Abstract base class for configuration option's.
266
267     Reminder: an Option object is **not** a container for the value.
268     """
269     __slots__ = ('_multi', '_validator', '_default_multi', '_default',
270                  '_state_callback', '_callback', '_multitype',
271                  '_consistencies', '_warnings_only', '_master_slaves',
272                  '_state_consistencies', '__weakref__')
273     _empty = ''
274
275     def __init__(self, name, doc, default=None, default_multi=None,
276                  requires=None, multi=False, callback=None,
277                  callback_params=None, validator=None, validator_params=None,
278                  properties=None, warnings_only=False):
279         """
280         :param name: the option's name
281         :param doc: the option's description
282         :param default: specifies the default value of the option,
283                         for a multi : ['bla', 'bla', 'bla']
284         :param default_multi: 'bla' (used in case of a reset to default only at
285                         a given index)
286         :param requires: is a list of names of options located anywhere
287                          in the configuration.
288         :param multi: if true, the option's value is a list
289         :param callback: the name of a function. If set, the function's output
290                          is responsible of the option's value
291         :param callback_params: the callback's parameter
292         :param validator: the name of a function which stands for a custom
293                           validation of the value
294         :param validator_params: the validator's parameters
295         :param properties: tuple of default properties
296         :param warnings_only: _validator and _consistencies don't raise if True
297                              Values()._warning contain message
298
299         """
300         super(Option, self).__init__(name, doc, requires, properties)
301         self._multi = multi
302         if validator is not None:
303             validate_callback(validator, validator_params, 'validator')
304             self._validator = (validator, validator_params)
305         else:
306             self._validator = None
307         if not self._multi and default_multi is not None:
308             raise ValueError(_("a default_multi is set whereas multi is False"
309                              " in option: {0}").format(name))
310         if default_multi is not None:
311             try:
312                 self._validate(default_multi)
313             except ValueError as err:
314                 raise ValueError(_("invalid default_multi value {0} "
315                                    "for option {1}: {2}").format(
316                                        str(default_multi), name, err))
317         if callback is not None and (default is not None or
318                                      default_multi is not None):
319             raise ValueError(_("default value not allowed if option: {0} "
320                              "is calculated").format(name))
321         if callback is None and callback_params is not None:
322             raise ValueError(_("params defined for a callback function but "
323                              "no callback defined"
324                              " yet for option {0}").format(name))
325         if callback is not None:
326             validate_callback(callback, callback_params, 'callback')
327             self._callback = (callback, callback_params)
328         else:
329             self._callback = None
330         if self._multi:
331             if default is None:
332                 default = []
333             self._multitype = multitypes.default
334             self._default_multi = default_multi
335         self._warnings_only = warnings_only
336         self.impl_validate(default)
337         self._default = default
338         self._consistencies = None
339
340     def _launch_consistency(self, func, option, value, context, index,
341                             all_cons_opts):
342         """Launch consistency now
343
344         :param func: function name, this name should start with _cons_
345         :type func: `str`
346         :param option: option that value is changing
347         :type option: `tiramisu.option.Option`
348         :param value: new value of this option
349         :param context: Config's context, if None, check default value instead
350         :type context: `tiramisu.config.Config`
351         :param index: only for multi option, consistency should be launch for
352                       specified index
353         :type index: `int`
354         :param all_cons_opts: all options concerne by this consistency
355         :type all_cons_opts: `list` of `tiramisu.option.Option`
356         """
357         if context is not None:
358             descr = context.cfgimpl_get_description()
359         #option is also in all_cons_opts
360         if option not in all_cons_opts:
361             raise ConfigError(_('option not in all_cons_opts'))
362
363         all_cons_vals = []
364         for opt in all_cons_opts:
365             #get value
366             if option == opt:
367                 opt_value = value
368             else:
369                 #if context, calculate value, otherwise get default value
370                 if context is not None:
371                     opt_value = context._getattr(
372                         descr.impl_get_path_by_opt(opt), validate=False)
373                 else:
374                     opt_value = opt.impl_getdefault()
375
376             #append value
377             if not self.impl_is_multi() or option == opt:
378                 all_cons_vals.append(opt_value)
379             else:
380                 #value is not already set, could be higher index
381                 try:
382                     all_cons_vals.append(opt_value[index])
383                 except IndexError:
384                     #so return if no value
385                     return
386         getattr(self, func)(all_cons_opts, all_cons_vals)
387
388     def impl_validate(self, value, context=None, validate=True,
389                       force_index=None):
390         """
391         :param value: the option's value
392         :param context: Config's context
393         :type context: :class:`tiramisu.config.Config`
394         :param validate: if true enables ``self._validator`` validation
395         :type validate: boolean
396         :param force_no_multi: if multi, value has to be a list
397                                not if force_no_multi is True
398         :type force_no_multi: boolean
399         """
400         if not validate:
401             return
402
403         def val_validator(val):
404             if self._validator is not None:
405                 if self._validator[1] is not None:
406                     validator_params = deepcopy(self._validator[1])
407                     if '' in validator_params:
408                         lst = list(validator_params[''])
409                         lst.insert(0, val)
410                         validator_params[''] = tuple(lst)
411                     else:
412                         validator_params[''] = (val,)
413                 else:
414                     validator_params = {'': (val,)}
415                 # Raise ValueError if not valid
416                 carry_out_calculation(self._name, config=context,
417                                       callback=self._validator[0],
418                                       callback_params=validator_params)
419
420         def do_validation(_value, _index=None):
421             if _value is None:
422                 return
423             # option validation
424             try:
425                 self._validate(_value)
426             except ValueError as err:
427                 raise ValueError(_('invalid value for option {0}: {1}'
428                                    '').format(self._name, err))
429             try:
430                 # valid with self._validator
431                 val_validator(_value)
432                 # if not context launch consistency validation
433                 if context is not None:
434                     descr._valid_consistency(self, _value, context, _index)
435                 self._second_level_validation(_value)
436             except ValueError as err:
437                 msg = _("invalid value for option {0}: {1}").format(
438                     self._name, err)
439                 if self._warnings_only:
440                     warnings.warn_explicit(ValueWarning(msg, self),
441                                            ValueWarning,
442                                            self.__class__.__name__, 0)
443                 else:
444                     raise ValueError(msg)
445
446         # generic calculation
447         if context is not None:
448             descr = context.cfgimpl_get_description()
449
450         if not self._multi or force_index is not None:
451             do_validation(value, force_index)
452         else:
453             if not isinstance(value, list):
454                 raise ValueError(_("which must be a list").format(value,
455                                                                   self._name))
456             for index, val in enumerate(value):
457                 do_validation(val, index)
458
459     def impl_getdefault(self, default_multi=False):
460         "accessing the default value"
461         if not default_multi or not self.impl_is_multi():
462             return self._default
463         else:
464             return self.getdefault_multi()
465
466     def impl_getdefault_multi(self):
467         "accessing the default value for a multi"
468         return self._default_multi
469
470     def impl_get_multitype(self):
471         return self._multitype
472
473     def impl_get_master_slaves(self):
474         return self._master_slaves
475
476     def impl_is_empty_by_default(self):
477         "no default value has been set yet"
478         if ((not self.impl_is_multi() and self._default is None) or
479                 (self.impl_is_multi() and (self._default == []
480                                            or None in self._default))):
481             return True
482         return False
483
484     def impl_getdoc(self):
485         "accesses the Option's doc"
486         return self.impl_get_information('doc')
487
488     def impl_has_callback(self):
489         "to know if a callback has been defined or not"
490         if self._callback is None:
491             return False
492         else:
493             return True
494
495     def impl_getkey(self, value):
496         return value
497
498     def impl_is_multi(self):
499         return self._multi
500
501     def impl_add_consistency(self, func, *other_opts):
502         """Add consistency means that value will be validate with other_opts
503         option's values.
504
505         :param func: function's name
506         :type func: `str`
507         :param other_opts: options used to validate value
508         :type other_opts: `list` of `tiramisu.option.Option`
509         """
510         if self._consistencies is None:
511             self._consistencies = []
512         for opt in other_opts:
513             if not isinstance(opt, Option):
514                 raise ConfigError(_('consistency should be set with an option'))
515             if self is opt:
516                 raise ConfigError(_('cannot add consistency with itself'))
517             if self.impl_is_multi() != opt.impl_is_multi():
518                 raise ConfigError(_('every options in consistency should be '
519                                     'multi or none'))
520         func = '_cons_{0}'.format(func)
521         all_cons_opts = tuple([self] + list(other_opts))
522         value = self.impl_getdefault()
523         if value is not None:
524             if self.impl_is_multi():
525                 for idx, val in enumerate(value):
526                     self._launch_consistency(func, self, val, None,
527                                              idx, all_cons_opts)
528             else:
529                 self._launch_consistency(func, self, value, None,
530                                          None, all_cons_opts)
531         self._consistencies.append((func, all_cons_opts))
532         self.impl_validate(self.impl_getdefault())
533
534     def _cons_not_equal(self, opts, vals):
535         for idx_inf, val_inf in enumerate(vals):
536             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
537                 if val_inf == val_sup is not None:
538                     raise ValueError(_("same value for {0} and {1}").format(
539                         opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
540
541     def _impl_convert_callbacks(self, descr, load=False):
542         if not load and self._callback is None:
543             self._state_callback = None
544         elif load and self._state_callback is None:
545             self._callback = None
546             del(self._state_callback)
547         else:
548             if load:
549                 callback, callback_params = self._state_callback
550             else:
551                 callback, callback_params = self._callback
552             if callback_params is not None:
553                 cllbck_prms = {}
554                 for key, values in callback_params.items():
555                     vls = []
556                     for value in values:
557                         if isinstance(value, tuple):
558                             if load:
559                                 value = (descr.impl_get_opt_by_path(value[0]),
560                                          value[1])
561                             else:
562                                 value = (descr.impl_get_path_by_opt(value[0]),
563                                          value[1])
564                         vls.append(value)
565                     cllbck_prms[key] = tuple(vls)
566             else:
567                 cllbck_prms = None
568
569             if load:
570                 del(self._state_callback)
571                 self._callback = (callback, cllbck_prms)
572             else:
573                 self._state_callback = (callback, cllbck_prms)
574
575     # serialize/unserialize
576     def _impl_convert_consistencies(self, descr, load=False):
577         """during serialization process, many things have to be done.
578         one of them is the localisation of the options.
579         The paths are set once for all.
580
581         :type descr: :class:`tiramisu.option.OptionDescription`
582         :param load: `True` if we are at the init of the option description
583         :type load: bool
584         """
585         if not load and self._consistencies is None:
586             self._state_consistencies = None
587         elif load and self._state_consistencies is None:
588             self._consistencies = None
589             del(self._state_consistencies)
590         else:
591             if load:
592                 consistencies = self._state_consistencies
593             else:
594                 consistencies = self._consistencies
595             if isinstance(consistencies, list):
596                 new_value = []
597                 for consistency in consistencies:
598                     values = []
599                     for obj in consistency[1]:
600                         if load:
601                             values.append(descr.impl_get_opt_by_path(obj))
602                         else:
603                             values.append(descr.impl_get_path_by_opt(obj))
604                     new_value.append((consistency[0], tuple(values)))
605
606             else:
607                 new_value = {}
608                 for key, _consistencies in consistencies.items():
609                     new_value[key] = []
610                     for key_cons, _cons in _consistencies:
611                         _list_cons = []
612                         for _con in _cons:
613                             if load:
614                                 _list_cons.append(
615                                     descr.impl_get_opt_by_path(_con))
616                             else:
617                                 _list_cons.append(
618                                     descr.impl_get_path_by_opt(_con))
619                         new_value[key].append((key_cons, tuple(_list_cons)))
620             if load:
621                 del(self._state_consistencies)
622                 self._consistencies = new_value
623             else:
624                 self._state_consistencies = new_value
625
626     def _second_level_validation(self, value):
627         pass
628
629
630 class ChoiceOption(Option):
631     """represents a choice out of several objects.
632
633     The option can also have the value ``None``
634     """
635
636     __slots__ = ('_values', '_open_values')
637     _opt_type = 'string'
638
639     def __init__(self, name, doc, values, default=None, default_multi=None,
640                  requires=None, multi=False, callback=None,
641                  callback_params=None, open_values=False, validator=None,
642                  validator_params=None, properties=None, warnings_only=False):
643         """
644         :param values: is a list of values the option can possibly take
645         """
646         if not isinstance(values, tuple):
647             raise TypeError(_('values must be a tuple for {0}').format(name))
648         self._values = values
649         if open_values not in (True, False):
650             raise TypeError(_('open_values must be a boolean for '
651                             '{0}').format(name))
652         self._open_values = open_values
653         super(ChoiceOption, self).__init__(name, doc, default=default,
654                                            default_multi=default_multi,
655                                            callback=callback,
656                                            callback_params=callback_params,
657                                            requires=requires,
658                                            multi=multi,
659                                            validator=validator,
660                                            validator_params=validator_params,
661                                            properties=properties,
662                                            warnings_only=warnings_only)
663
664     def impl_get_values(self):
665         return self._values
666
667     def impl_is_openvalues(self):
668         return self._open_values
669
670     def _validate(self, value):
671         if not self._open_values and not value in self._values:
672             raise ValueError(_('value {0} is not permitted, '
673                                'only {1} is allowed'
674                                '').format(value, self._values))
675
676
677 class BoolOption(Option):
678     "represents a choice between ``True`` and ``False``"
679     __slots__ = tuple()
680     _opt_type = 'bool'
681
682     def _validate(self, value):
683         if not isinstance(value, bool):
684             raise ValueError(_('invalid boolean'))
685
686
687 class IntOption(Option):
688     "represents a choice of an integer"
689     __slots__ = tuple()
690     _opt_type = 'int'
691
692     def _validate(self, value):
693         if not isinstance(value, int):
694             raise ValueError(_('invalid integer'))
695
696
697 class FloatOption(Option):
698     "represents a choice of a floating point number"
699     __slots__ = tuple()
700     _opt_type = 'float'
701
702     def _validate(self, value):
703         if not isinstance(value, float):
704             raise ValueError(_('invalid float'))
705
706
707 class StrOption(Option):
708     "represents the choice of a string"
709     __slots__ = tuple()
710     _opt_type = 'string'
711
712     def _validate(self, value):
713         if not isinstance(value, str):
714             raise ValueError(_('invalid string'))
715
716
717 if sys.version_info[0] >= 3:
718     #UnicodeOption is same has StrOption in python 3+
719     class UnicodeOption(StrOption):
720         __slots__ = tuple()
721         pass
722 else:
723     class UnicodeOption(Option):
724         "represents the choice of a unicode string"
725         __slots__ = tuple()
726         _opt_type = 'unicode'
727         _empty = u''
728
729         def _validate(self, value):
730             if not isinstance(value, unicode):
731                 raise ValueError(_('invalid unicode'))
732
733
734 class SymLinkOption(BaseOption):
735     __slots__ = ('_name', '_opt', '_state_opt')
736     _opt_type = 'symlink'
737     #not return _opt consistencies
738     _consistencies = None
739
740     def __init__(self, name, opt):
741         self._name = name
742         if not isinstance(opt, Option):
743             raise ValueError(_('malformed symlinkoption '
744                                'must be an option '
745                                'for symlink {0}').format(name))
746         self._opt = opt
747         self._readonly = True
748
749     def __getattr__(self, name):
750         if name in ('_name', '_opt', '_opt_type', '_readonly'):
751             return object.__getattr__(self, name)
752         else:
753             return getattr(self._opt, name)
754
755     def _impl_getstate(self, descr):
756         super(SymLinkOption, self)._impl_getstate(descr)
757         self._state_opt = descr.impl_get_path_by_opt(self._opt)
758
759     def _impl_setstate(self, descr):
760         self._opt = descr.impl_get_opt_by_path(self._state_opt)
761         del(self._state_opt)
762         super(SymLinkOption, self)._impl_setstate(descr)
763
764
765 class IPOption(Option):
766     "represents the choice of an ip"
767     __slots__ = ('_private_only', '_allow_reserved')
768     _opt_type = 'ip'
769
770     def __init__(self, name, doc, default=None, default_multi=None,
771                  requires=None, multi=False, callback=None,
772                  callback_params=None, validator=None, validator_params=None,
773                  properties=None, private_only=False, allow_reserved=False,
774                  warnings_only=False):
775         self._private_only = private_only
776         self._allow_reserved = allow_reserved
777         super(IPOption, self).__init__(name, doc, default=default,
778                                        default_multi=default_multi,
779                                        callback=callback,
780                                        callback_params=callback_params,
781                                        requires=requires,
782                                        multi=multi,
783                                        validator=validator,
784                                        validator_params=validator_params,
785                                        properties=properties,
786                                        warnings_only=warnings_only)
787
788     def _validate(self, value):
789         try:
790             IP('{0}/32'.format(value))
791         except ValueError:
792             raise ValueError(_('invalid IP'))
793
794     def _second_level_validation(self, value):
795         ip = IP('{0}/32'.format(value))
796         if not self._allow_reserved and ip.iptype() == 'RESERVED':
797             raise ValueError(_("invalid IP, mustn't not be in reserved class"))
798         if self._private_only and not ip.iptype() == 'PRIVATE':
799             raise ValueError(_("invalid IP, must be in private class"))
800
801
802 class PortOption(Option):
803     """represents the choice of a port
804     The port numbers are divided into three ranges:
805     the well-known ports,
806     the registered ports,
807     and the dynamic or private ports.
808     You can actived this three range.
809     Port number 0 is reserved and can't be used.
810     see: http://en.wikipedia.org/wiki/Port_numbers
811     """
812     __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
813     _opt_type = 'port'
814
815     def __init__(self, name, doc, default=None, default_multi=None,
816                  requires=None, multi=False, callback=None,
817                  callback_params=None, validator=None, validator_params=None,
818                  properties=None, allow_range=False, allow_zero=False,
819                  allow_wellknown=True, allow_registred=True,
820                  allow_private=False, warnings_only=False):
821         self._allow_range = allow_range
822         self._min_value = None
823         self._max_value = None
824         ports_min = [0, 1, 1024, 49152]
825         ports_max = [0, 1023, 49151, 65535]
826         is_finally = False
827         for index, allowed in enumerate([allow_zero,
828                                          allow_wellknown,
829                                          allow_registred,
830                                          allow_private]):
831             if self._min_value is None:
832                 if allowed:
833                     self._min_value = ports_min[index]
834             elif not allowed:
835                 is_finally = True
836             elif allowed and is_finally:
837                 raise ValueError(_('inconsistency in allowed range'))
838             if allowed:
839                 self._max_value = ports_max[index]
840
841         if self._max_value is None:
842             raise ValueError(_('max value is empty'))
843
844         super(PortOption, self).__init__(name, doc, default=default,
845                                          default_multi=default_multi,
846                                          callback=callback,
847                                          callback_params=callback_params,
848                                          requires=requires,
849                                          multi=multi,
850                                          validator=validator,
851                                          validator_params=validator_params,
852                                          properties=properties,
853                                          warnings_only=warnings_only)
854
855     def _validate(self, value):
856         if self._allow_range and ":" in str(value):
857             value = str(value).split(':')
858             if len(value) != 2:
859                 raise ValueError('invalid part, range must have two values '
860                                  'only')
861             if not value[0] < value[1]:
862                 raise ValueError('invalid port, first port in range must be'
863                                  ' smaller than the second one')
864         else:
865             value = [value]
866
867         for val in value:
868             if not self._min_value <= int(val) <= self._max_value:
869                 raise ValueError('invalid port, must be an between {0} and {1}'
870                                  ''.format(self._min_value, self._max_value))
871
872
873 class NetworkOption(Option):
874     "represents the choice of a network"
875     __slots__ = tuple()
876     _opt_type = 'network'
877
878     def _validate(self, value):
879         try:
880             IP(value)
881         except ValueError:
882             raise ValueError(_('invalid network address'))
883
884     def _second_level_validation(self, value):
885         ip = IP(value)
886         if ip.iptype() == 'RESERVED':
887             raise ValueError(_("invalid network address, must not be in reserved class"))
888
889
890 class NetmaskOption(Option):
891     "represents the choice of a netmask"
892     __slots__ = tuple()
893     _opt_type = 'netmask'
894
895     def _validate(self, value):
896         try:
897             IP('0.0.0.0/{0}'.format(value))
898         except ValueError:
899             raise ValueError(_('invalid netmask address'))
900
901     def _cons_network_netmask(self, opts, vals):
902         #opts must be (netmask, network) options
903         if None in vals:
904             return
905         self.__cons_netmask(opts, vals[0], vals[1], False)
906
907     def _cons_ip_netmask(self, opts, vals):
908         #opts must be (netmask, ip) options
909         if None in vals:
910             return
911         self.__cons_netmask(opts, vals[0], vals[1], True)
912
913     def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
914         if len(opts) != 2:
915             raise ConfigError(_('invalid len for opts'))
916         msg = None
917         try:
918             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
919                     make_net=make_net)
920             #if cidr == 32, ip same has network
921             if ip.prefixlen() != 32:
922                 try:
923                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
924                         make_net=not make_net)
925                 except ValueError:
926                     if not make_net:
927                         msg = _("invalid network {0} ({1}) "
928                                 "with netmask {2},"
929                                 " this network is an IP")
930                 else:
931                     if make_net:
932                         msg = _("invalid IP {0} ({1}) with netmask {2},"
933                                 " this IP is a network")
934
935         except ValueError:
936             if make_net:
937                 msg = _('invalid IP {0} ({1}) with netmask {2}')
938             else:
939                 msg = _('invalid network {0} ({1}) with netmask {2}')
940         if msg is not None:
941             raise ValueError(msg.format(val_ipnetwork, opts[1]._name,
942                                         val_netmask))
943
944
945 class BroadcastOption(Option):
946     __slots__ = tuple()
947     _opt_type = 'broadcast'
948
949     def _validate(self, value):
950         try:
951             IP('{0}/32'.format(value))
952         except ValueError:
953             raise ValueError(_('invalid broadcast address'))
954
955     def _cons_broadcast(self, opts, vals):
956         if len(vals) != 3:
957             raise ConfigError(_('invalid len for vals'))
958         if None in vals:
959             return
960         broadcast, network, netmask = vals
961         if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
962             raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
963                                '({3}) and netmask {4} ({5})').format(
964                                    broadcast, opts[0]._name, network,
965                                    opts[1]._name, netmask, opts[2]._name))
966
967
968 class DomainnameOption(Option):
969     """represents the choice of a domain name
970     netbios: for MS domain
971     hostname: to identify the device
972     domainname:
973     fqdn: with tld, not supported yet
974     """
975     __slots__ = ('_type', '_allow_ip', '_allow_without_dot', '_domain_re')
976     _opt_type = 'domainname'
977
978     def __init__(self, name, doc, default=None, default_multi=None,
979                  requires=None, multi=False, callback=None,
980                  callback_params=None, validator=None, validator_params=None,
981                  properties=None, allow_ip=False, type_='domainname',
982                  warnings_only=False, allow_without_dot=False):
983         if type_ not in ['netbios', 'hostname', 'domainname']:
984             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
985         self._type = type_
986         if allow_ip not in [True, False]:
987             raise ValueError(_('allow_ip must be a boolean'))
988         if allow_without_dot not in [True, False]:
989             raise ValueError(_('allow_without_dot must be a boolean'))
990         self._allow_ip = allow_ip
991         self._allow_without_dot = allow_without_dot
992         end = ''
993         extrachar = ''
994         if self._type == 'netbios':
995             length = 14
996         elif self._type == 'hostname':
997             length = 62
998         elif self._type == 'domainname':
999             length = 62
1000             extrachar = '\.'
1001             end = '+[a-z]*'
1002         self._domain_re = re.compile(r'^(?:[a-z][a-z\d\-]{{,{0}}}{1}){2}$'
1003                                      ''.format(length, extrachar, end))
1004         super(DomainnameOption, self).__init__(name, doc, default=default,
1005                                                default_multi=default_multi,
1006                                                callback=callback,
1007                                                callback_params=callback_params,
1008                                                requires=requires,
1009                                                multi=multi,
1010                                                validator=validator,
1011                                                validator_params=validator_params,
1012                                                properties=properties,
1013                                                warnings_only=warnings_only)
1014
1015     def _validate(self, value):
1016         if self._allow_ip is True:
1017             try:
1018                 IP('{0}/32'.format(value))
1019                 return
1020             except ValueError:
1021                 pass
1022         if self._type == 'domainname' and not self._allow_without_dot and \
1023                 '.' not in value:
1024             raise ValueError(_("invalid domainname, must have dot"))
1025             if len(value) > 255:
1026                 raise ValueError(_("invalid domainname's length (max 255)"))
1027         if len(value) < 2:
1028             raise ValueError(_("invalid domainname's length (min 2)"))
1029         if not self._domain_re.search(value):
1030             raise ValueError(_('invalid domainname'))
1031
1032
1033 class EmailOption(DomainnameOption):
1034     __slots__ = tuple()
1035     _opt_type = 'email'
1036     username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1037
1038     def __init__(self, *args, **kwargs):
1039         kwargs['type_'] = 'domainname'
1040         kwargs['allow_ip'] = False
1041         kwargs['allow_without_dot'] = False
1042         super(EmailOption, self).__init__(*args, **kwargs)
1043
1044     def _validate(self, value):
1045         splitted = value.split('@', 1)
1046         try:
1047             username, domain = splitted
1048         except ValueError:
1049             raise ValueError(_('invalid email address, should contains one @'
1050                                ))
1051         if not self.username_re.search(username):
1052             raise ValueError(_('invalid username in email address'))
1053         super(EmailOption, self)._validate(domain)
1054
1055
1056 class URLOption(DomainnameOption):
1057     __slots__ = tuple()
1058     _opt_type = 'url'
1059     proto_re = re.compile(r'(http|https)://')
1060     path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
1061
1062     def __init__(self, *args, **kwargs):
1063         kwargs['type_'] = 'domainname'
1064         kwargs['allow_ip'] = False
1065         kwargs['allow_without_dot'] = False
1066         super(URLOption, self).__init__(*args, **kwargs)
1067
1068     def _validate(self, value):
1069         match = self.proto_re.search(value)
1070         if not match:
1071             raise ValueError(_('invalid url, should start with http:// or '
1072                                'https://'))
1073         value = value[len(match.group(0)):]
1074         # get domain/files
1075         splitted = value.split('/', 1)
1076         try:
1077             domain, files = splitted
1078         except ValueError:
1079             domain = value
1080             files = None
1081         # if port in domain
1082         splitted = domain.split(':', 1)
1083         try:
1084             domain, port = splitted
1085
1086         except ValueError:
1087             domain = splitted[0]
1088             port = 0
1089         if not 0 <= int(port) <= 65535:
1090             raise ValueError(_('invalid url, port must be an between 0 and '
1091                                '65536'))
1092         # validate domainname
1093         super(URLOption, self)._validate(domain)
1094         # validate file
1095         if files is not None and files != '' and not self.path_re.search(files):
1096             raise ValueError(_('invalid url, should ends with filename'))
1097
1098
1099 class FilenameOption(Option):
1100     __slots__ = tuple()
1101     _opt_type = 'file'
1102     path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
1103
1104     def _validate(self, value):
1105         match = self.path_re.search(value)
1106         if not match:
1107             raise ValueError(_('invalid filename'))
1108
1109
1110 class OptionDescription(BaseOption):
1111     """Config's schema (organisation, group) and container of Options
1112     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
1113     """
1114     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
1115                  '_state_group_type', '_properties', '_children',
1116                  '_cache_consistencies', '_calc_properties', '__weakref__',
1117                  '_readonly', '_impl_informations', '_state_requires',
1118                  '_stated', '_state_readonly')
1119     _opt_type = 'optiondescription'
1120
1121     def __init__(self, name, doc, children, requires=None, properties=None):
1122         """
1123         :param children: a list of options (including optiondescriptions)
1124
1125         """
1126         super(OptionDescription, self).__init__(name, doc, requires, properties)
1127         child_names = [child._name for child in children]
1128         #better performance like this
1129         valid_child = copy(child_names)
1130         valid_child.sort()
1131         old = None
1132         for child in valid_child:
1133             if child == old:
1134                 raise ConflictError(_('duplicate option name: '
1135                                       '{0}').format(child))
1136             old = child
1137         self._children = (tuple(child_names), tuple(children))
1138         self._cache_paths = None
1139         self._cache_consistencies = None
1140         # the group_type is useful for filtering OptionDescriptions in a config
1141         self._group_type = groups.default
1142
1143     def impl_getdoc(self):
1144         return self.impl_get_information('doc')
1145
1146     def __getattr__(self, name):
1147         if name in self.__slots__:
1148             return object.__getattribute__(self, name)
1149         try:
1150             return self._children[1][self._children[0].index(name)]
1151         except ValueError:
1152             raise AttributeError(_('unknown Option {0} '
1153                                    'in OptionDescription {1}'
1154                                    '').format(name, self._name))
1155
1156     def impl_getkey(self, config):
1157         return tuple([child.impl_getkey(getattr(config, child._name))
1158                       for child in self.impl_getchildren()])
1159
1160     def impl_getpaths(self, include_groups=False, _currpath=None):
1161         """returns a list of all paths in self, recursively
1162            _currpath should not be provided (helps with recursion)
1163         """
1164         if _currpath is None:
1165             _currpath = []
1166         paths = []
1167         for option in self.impl_getchildren():
1168             attr = option._name
1169             if isinstance(option, OptionDescription):
1170                 if include_groups:
1171                     paths.append('.'.join(_currpath + [attr]))
1172                 paths += option.impl_getpaths(include_groups=include_groups,
1173                                               _currpath=_currpath + [attr])
1174             else:
1175                 paths.append('.'.join(_currpath + [attr]))
1176         return paths
1177
1178     def impl_getchildren(self):
1179         return self._children[1]
1180
1181     def impl_build_cache(self,
1182                          cache_path=None,
1183                          cache_option=None,
1184                          _currpath=None,
1185                          _consistencies=None,
1186                          force_no_consistencies=False):
1187         if _currpath is None and self._cache_paths is not None:
1188             # cache already set
1189             return
1190         if _currpath is None:
1191             save = True
1192             _currpath = []
1193             if not force_no_consistencies:
1194                 _consistencies = {}
1195         else:
1196             save = False
1197         if cache_path is None:
1198             cache_path = []
1199             cache_option = []
1200         for option in self.impl_getchildren():
1201             attr = option._name
1202             if option in cache_option:
1203                 raise ConflictError(_('duplicate option: {0}').format(option))
1204
1205             cache_option.append(option)
1206             if not force_no_consistencies:
1207                 option._readonly = True
1208             cache_path.append(str('.'.join(_currpath + [attr])))
1209             if not isinstance(option, OptionDescription):
1210                 if not force_no_consistencies and \
1211                         option._consistencies is not None:
1212                     for consistency in option._consistencies:
1213                         func, all_cons_opts = consistency
1214                         for opt in all_cons_opts:
1215                             _consistencies.setdefault(opt,
1216                                                       []).append((func,
1217                                                                   all_cons_opts))
1218             else:
1219                 _currpath.append(attr)
1220                 option.impl_build_cache(cache_path,
1221                                         cache_option,
1222                                         _currpath,
1223                                         _consistencies,
1224                                         force_no_consistencies)
1225                 _currpath.pop()
1226         if save:
1227             self._cache_paths = (tuple(cache_option), tuple(cache_path))
1228             if not force_no_consistencies:
1229                 if _consistencies != {}:
1230                     self._cache_consistencies = {}
1231                     for opt, cons in _consistencies.items():
1232                         if opt not in cache_option:
1233                             raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
1234                         self._cache_consistencies[opt] = tuple(cons)
1235                 self._readonly = True
1236
1237     def impl_get_opt_by_path(self, path):
1238         try:
1239             return self._cache_paths[0][self._cache_paths[1].index(path)]
1240         except ValueError:
1241             raise AttributeError(_('no option for path {0}').format(path))
1242
1243     def impl_get_path_by_opt(self, opt):
1244         try:
1245             return self._cache_paths[1][self._cache_paths[0].index(opt)]
1246         except ValueError:
1247             raise AttributeError(_('no option {0} found').format(opt))
1248
1249     # ____________________________________________________________
1250     def impl_set_group_type(self, group_type):
1251         """sets a given group object to an OptionDescription
1252
1253         :param group_type: an instance of `GroupType` or `MasterGroupType`
1254                               that lives in `setting.groups`
1255         """
1256         if self._group_type != groups.default:
1257             raise TypeError(_('cannot change group_type if already set '
1258                             '(old {0}, new {1})').format(self._group_type,
1259                                                          group_type))
1260         if isinstance(group_type, groups.GroupType):
1261             self._group_type = group_type
1262             if isinstance(group_type, groups.MasterGroupType):
1263                 #if master (same name has group) is set
1264                 identical_master_child_name = False
1265                 #for collect all slaves
1266                 slaves = []
1267                 master = None
1268                 for child in self.impl_getchildren():
1269                     if isinstance(child, OptionDescription):
1270                         raise ValueError(_("master group {0} shall not have "
1271                                          "a subgroup").format(self._name))
1272                     if isinstance(child, SymLinkOption):
1273                         raise ValueError(_("master group {0} shall not have "
1274                                          "a symlinkoption").format(self._name))
1275                     if not child.impl_is_multi():
1276                         raise ValueError(_("not allowed option {0} "
1277                                          "in group {1}"
1278                                          ": this option is not a multi"
1279                                          "").format(child._name, self._name))
1280                     if child._name == self._name:
1281                         identical_master_child_name = True
1282                         child._multitype = multitypes.master
1283                         master = child
1284                     else:
1285                         slaves.append(child)
1286                 if master is None:
1287                     raise ValueError(_('master group with wrong'
1288                                        ' master name for {0}'
1289                                        ).format(self._name))
1290                 master._master_slaves = tuple(slaves)
1291                 for child in self.impl_getchildren():
1292                     if child != master:
1293                         child._master_slaves = master
1294                         child._multitype = multitypes.slave
1295                 if not identical_master_child_name:
1296                     raise ValueError(_("no child has same nom has master group"
1297                                        " for: {0}").format(self._name))
1298         else:
1299             raise ValueError(_('group_type: {0}'
1300                                ' not allowed').format(group_type))
1301
1302     def impl_get_group_type(self):
1303         return self._group_type
1304
1305     def _valid_consistency(self, option, value, context, index):
1306         if self._cache_consistencies is None:
1307             return True
1308         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1309         consistencies = self._cache_consistencies.get(option)
1310         if consistencies is not None:
1311             for func, all_cons_opts in consistencies:
1312                 #all_cons_opts[0] is the option where func is set
1313                 ret = all_cons_opts[0]._launch_consistency(func, option,
1314                                                            value,
1315                                                            context, index,
1316                                                            all_cons_opts)
1317                 if ret is False:
1318                     return False
1319         return True
1320
1321     def _impl_getstate(self, descr=None):
1322         """enables us to export into a dict
1323         :param descr: parent :class:`tiramisu.option.OptionDescription`
1324         """
1325         if descr is None:
1326             self.impl_build_cache()
1327             descr = self
1328         super(OptionDescription, self)._impl_getstate(descr)
1329         self._state_group_type = str(self._group_type)
1330         for option in self.impl_getchildren():
1331             option._impl_getstate(descr)
1332
1333     def __getstate__(self):
1334         """special method to enable the serialization with pickle
1335         """
1336         stated = True
1337         try:
1338             # the `_state` attribute is a flag that which tells us if
1339             # the serialization can be performed
1340             self._stated
1341         except AttributeError:
1342             # if cannot delete, _impl_getstate never launch
1343             # launch it recursivement
1344             # _stated prevent __getstate__ launch more than one time
1345             # _stated is delete, if re-serialize, re-lauch _impl_getstate
1346             self._impl_getstate()
1347             stated = False
1348         return super(OptionDescription, self).__getstate__(stated)
1349
1350     def _impl_setstate(self, descr=None):
1351         """enables us to import from a dict
1352         :param descr: parent :class:`tiramisu.option.OptionDescription`
1353         """
1354         if descr is None:
1355             self._cache_paths = None
1356             self._cache_consistencies = None
1357             self.impl_build_cache(force_no_consistencies=True)
1358             descr = self
1359         self._group_type = getattr(groups, self._state_group_type)
1360         del(self._state_group_type)
1361         super(OptionDescription, self)._impl_setstate(descr)
1362         for option in self.impl_getchildren():
1363             option._impl_setstate(descr)
1364
1365     def __setstate__(self, state):
1366         super(OptionDescription, self).__setstate__(state)
1367         try:
1368             self._stated
1369         except AttributeError:
1370             self._impl_setstate()
1371
1372
1373 def validate_requires_arg(requires, name):
1374     """check malformed requirements
1375     and tranform dict to internal tuple
1376
1377     :param requires: have a look at the
1378                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1379                      know more about
1380                      the description of the requires dictionary
1381     """
1382     if requires is None:
1383         return None, None
1384     ret_requires = {}
1385     config_action = {}
1386
1387     # start parsing all requires given by user (has dict)
1388     # transforme it to a tuple
1389     for require in requires:
1390         if not type(require) == dict:
1391             raise ValueError(_("malformed requirements type for option:"
1392                                " {0}, must be a dict").format(name))
1393         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1394                       'same_action')
1395         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1396         if unknown_keys != frozenset():
1397             raise ValueError('malformed requirements for option: {0}'
1398                              ' unknown keys {1}, must only '
1399                              '{2}'.format(name,
1400                                           unknown_keys,
1401                                           valid_keys))
1402         # prepare all attributes
1403         try:
1404             option = require['option']
1405             expected = require['expected']
1406             action = require['action']
1407         except KeyError:
1408             raise ValueError(_("malformed requirements for option: {0}"
1409                                " require must have option, expected and"
1410                                " action keys").format(name))
1411         inverse = require.get('inverse', False)
1412         if inverse not in [True, False]:
1413             raise ValueError(_('malformed requirements for option: {0}'
1414                                ' inverse must be boolean'))
1415         transitive = require.get('transitive', True)
1416         if transitive not in [True, False]:
1417             raise ValueError(_('malformed requirements for option: {0}'
1418                                ' transitive must be boolean'))
1419         same_action = require.get('same_action', True)
1420         if same_action not in [True, False]:
1421             raise ValueError(_('malformed requirements for option: {0}'
1422                                ' same_action must be boolean'))
1423
1424         if not isinstance(option, Option):
1425             raise ValueError(_('malformed requirements '
1426                                'must be an option in option {0}').format(name))
1427         if option.impl_is_multi():
1428             raise ValueError(_('malformed requirements option {0} '
1429                                'should not be a multi').format(name))
1430         if expected is not None:
1431             try:
1432                 option._validate(expected)
1433             except ValueError as err:
1434                 raise ValueError(_('malformed requirements second argument '
1435                                    'must be valid for option {0}'
1436                                    ': {1}').format(name, err))
1437         if action in config_action:
1438             if inverse != config_action[action]:
1439                 raise ValueError(_("inconsistency in action types"
1440                                    " for option: {0}"
1441                                    " action: {1}").format(name, action))
1442         else:
1443             config_action[action] = inverse
1444         if action not in ret_requires:
1445             ret_requires[action] = {}
1446         if option not in ret_requires[action]:
1447             ret_requires[action][option] = (option, [expected], action,
1448                                             inverse, transitive, same_action)
1449         else:
1450             ret_requires[action][option][1].append(expected)
1451     # transform dict to tuple
1452     ret = []
1453     for opt_requires in ret_requires.values():
1454         ret_action = []
1455         for require in opt_requires.values():
1456             ret_action.append((require[0], tuple(require[1]), require[2],
1457                                require[3], require[4], require[5]))
1458         ret.append(tuple(ret_action))
1459     return frozenset(config_action.keys()), tuple(ret)
1460
1461
1462 def validate_callback(callback, callback_params, type_):
1463     if type(callback) != FunctionType:
1464         raise ValueError(_('{0} should be a function').format(type_))
1465     if callback_params is not None:
1466         if not isinstance(callback_params, dict):
1467             raise ValueError(_('{0}_params should be a dict').format(type_))
1468         for key, callbacks in callback_params.items():
1469             if key != '' and len(callbacks) != 1:
1470                 raise ValueError(_('{0}_params with key {1} should not have '
1471                                    'length different to 1').format(type_,
1472                                                                    key))
1473             if not isinstance(callbacks, tuple):
1474                 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1475                                    ).format(type_, key))
1476             for callbk in callbacks:
1477                 if isinstance(callbk, tuple):
1478                     option, force_permissive = callbk
1479                     if type_ == 'validator' and not force_permissive:
1480                         raise ValueError(_('validator not support tuple'))
1481                     if not isinstance(option, Option) and not \
1482                             isinstance(option, SymLinkOption):
1483                         raise ValueError(_('{0}_params should have an option '
1484                                            'not a {0} for first argument'
1485                                            ).format(type_, type(option)))
1486                     if force_permissive not in [True, False]:
1487                         raise ValueError(_('{0}_params should have a boolean'
1488                                            ' not a {0} for second argument'
1489                                            ).format(type_, type(
1490                                                force_permissive)))