e645a31ae992b9c7aaa0590ea20d57df7dfd0039
[tiramisu.git] / tiramisu / option.py
1 # -*- coding: utf-8 -*-
2 "option types and option description for the configuration management"
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 from copy import copy, deepcopy
25 from types import FunctionType
26 from IPy import IP
27
28 from tiramisu.error import ConflictError
29 from tiramisu.setting import groups, multitypes
30 from tiramisu.i18n import _
31 from tiramisu.autolib import carry_out_calculation
32
33 name_regexp = re.compile(r'^\d+')
34 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
35                    'make_dict', 'unwrap_from_path', 'read_only',
36                    'read_write', 'getowner', 'set_contexts')
37
38
39 def valid_name(name):
40     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
41     try:
42         name = str(name)
43     except:
44         return False
45     if re.match(name_regexp, name) is None and not name.startswith('_') \
46             and name not in forbidden_names \
47             and not name.startswith('impl_') \
48             and not name.startswith('cfgimpl_'):
49         return True
50     else:
51         return False
52 #____________________________________________________________
53 #
54
55
56 class BaseInformation(object):
57     "interface for an option's information attribute"
58     __slots__ = ('_impl_informations',)
59
60     def impl_set_information(self, key, value):
61         """updates the information's attribute
62         (wich is a dictionnary)
63
64         :param key: information's key (ex: "help", "doc"
65         :param value: information's value (ex: "the help string")
66         """
67         try:
68             self._impl_informations[key] = value
69         except AttributeError:
70             raise AttributeError(_('{0} instance has no attribute '
71                                    'impl_set_information').format(self.__class__.__name__))
72
73     def impl_get_information(self, key, default=None):
74         """retrieves one information's item
75
76         :param key: the item string (ex: "help")
77         """
78         try:
79             if key in self._impl_informations:
80                 return self._impl_informations[key]
81             elif default is not None:
82                 return default
83             else:
84                 raise ValueError(_("Information's item not found: {0}").format(key))
85         except AttributeError:
86             raise AttributeError(_('{0} instance has no attribute '
87                                    'impl_get_information').format(self.__class__.__name__))
88
89
90 class Option(BaseInformation):
91     """
92     Abstract base class for configuration option's.
93
94     Reminder: an Option object is **not** a container for the value
95     """
96     __slots__ = ('_name', '_requires', '_multi', '_validator', '_default_multi',
97                  '_default', '_properties', '_callback', '_multitype',
98                  '_master_slaves', '_consistencies', '_empty')
99     _empty = ''
100
101     def __init__(self, name, doc, default=None, default_multi=None,
102                  requires=None, multi=False, callback=None,
103                  callback_params=None, validator=None, validator_args=None,
104                  properties=None):
105         """
106         :param name: the option's name
107         :param doc: the option's description
108         :param default: specifies the default value of the option,
109                         for a multi : ['bla', 'bla', 'bla']
110         :param default_multi: 'bla' (used in case of a reset to default only at
111                         a given index)
112         :param requires: is a list of names of options located anywhere
113                          in the configuration.
114         :param multi: if true, the option's value is a list
115         :param callback: the name of a function. If set, the function's output
116                          is responsible of the option's value
117         :param callback_params: the callback's parameter
118         :param validator: the name of a function wich stands for a custom
119                           validation of the value
120         :param validator_args: the validator's parameters
121
122         """
123         if not valid_name(name):
124             raise ValueError(_("invalid name: {0} for option").format(name))
125         self._name = name
126         self._impl_informations = {}
127         self.impl_set_information('doc', doc)
128         requires = validate_requires_arg(requires, self._name)
129         self._requires = requires
130         self._multi = multi
131         self._consistencies = None
132         if validator is not None:
133             if type(validator) != FunctionType:
134                 raise TypeError(_("validator must be a function"))
135             if validator_args is None:
136                 validator_args = {}
137             self._validator = (validator, validator_args)
138         else:
139             self._validator = None
140         if not self._multi and default_multi is not None:
141             raise ValueError(_("a default_multi is set whereas multi is False"
142                              " in option: {0}").format(name))
143         if default_multi is not None and not self._validate(default_multi):
144             raise ValueError(_("invalid default_multi value {0} "
145                              "for option {1}").format(str(default_multi), name))
146         if callback is not None and (default is not None or default_multi is not None):
147             raise ValueError(_("defaut values not allowed if option: {0} "
148                              "is calculated").format(name))
149         if callback is None and callback_params is not None:
150             raise ValueError(_("params defined for a callback function but "
151                              "no callback defined yet for option {0}").format(name))
152         if callback is not None:
153             if type(callback) != FunctionType:
154                 raise ValueError('callback must be a function')
155             if callback_params is not None and \
156                     not isinstance(callback_params, dict):
157                 raise ValueError('callback_params must be a dict')
158             self._callback = (callback, callback_params)
159         else:
160             self._callback = None
161         if self._multi:
162             if default is None:
163                 default = []
164             self._multitype = multitypes.default
165             self._default_multi = default_multi
166         self.impl_validate(default)
167         self._default = default
168         if properties is None:
169             properties = tuple()
170         if not isinstance(properties, tuple):
171             raise TypeError(_('invalid properties type {0} for {1},'
172                             ' must be a tuple').format(type(properties), self._name))
173         self._properties = set(properties)  # 'hidden', 'disabled'...
174
175     def __eq__(self, other):
176         "Option comparison"
177         if not isinstance(other, Option):
178             return False
179         slots = list(self.__slots__ + Option.__slots__ + BaseInformation.__slots__)
180         for var in slots:
181             try:
182                 val1 = getattr(self, var)
183                 not_in1 = False
184             except:
185                 not_in1 = True
186             try:
187                 val2 = getattr(other, var)
188                 not_in2 = False
189             except:
190                 not_in2 = True
191             if True in (not_in1, not_in2):
192                 if not_in1 != not_in2:
193                     return False
194             elif val1 != val2:
195                 return False
196         return True
197
198     def __ne__(self, other):
199         if not isinstance(other, Option):
200             return False
201         return not self == other
202
203     def _launch_consistency(self, func, opt, vals, context, index, opt_):
204         if context is not None:
205             descr = context.cfgimpl_get_description()
206         if opt is self:
207             #values are for self, search opt_ values
208             values = vals
209             if context is not None:
210                 path = descr.impl_get_path_by_opt(opt_)
211                 values_ = context._getattr(path, validate=False)
212             else:
213                 values_ = opt_.impl_getdefault()
214             if index is not None:
215                 #value is not already set, could be higher
216                 try:
217                     values_ = values_[index]
218                 except IndexError:
219                     values_ = None
220         else:
221             #values are for opt_, search self values
222             values_ = vals
223             if context is not None:
224                 path = descr.impl_get_path_by_opt(self)
225                 values = context._getattr(path, validate=False)
226             else:
227                 values = self.impl_getdefault()
228             if index is not None:
229                 #value is not already set, could be higher
230                 try:
231                     values = values[index]
232                 except IndexError:
233                     values = None
234         if index is None and self.impl_is_multi():
235             for index in range(0, len(values)):
236                 try:
237                     value = values[index]
238                     value_ = values_[index]
239                 except IndexError:
240                     value = None
241                     value_ = None
242                 if None not in (value, value_):
243                     getattr(self, func)(opt_._name, value, value_)
244         else:
245             if None not in (values, values_):
246                 getattr(self, func)(opt_._name, values, values_)
247
248     def impl_validate(self, value, context=None, validate=True):
249         """
250         :param value: the option's value
251         :param validate: if true enables ``self._validator`` validation
252         """
253         if not validate:
254             return
255
256         def _val_validator(val):
257             callback_params = deepcopy(self._validator[1])
258             callback_params.setdefault('', []).insert(0, val)
259             return carry_out_calculation(self._name, config=context,
260                                          callback=self._validator[0],
261                                          callback_params=callback_params)
262
263         def val_validator():
264             #add current value has first argument
265             if self.impl_is_multi():
266                 for val in value:
267                     if not _val_validator(val):
268                         return False
269                 return True
270             else:
271                 return _val_validator(value)
272
273         # generic calculation
274         if context is not None:
275             descr = context.cfgimpl_get_description()
276         if not self._multi:
277             if value is not None and ((self._validator is not None and
278                                        not val_validator()) or
279                                       not self._validate(value)):
280                 raise ValueError(_("invalid value {0} for option {1}"
281                                    "").format(value, self._name))
282             if context is not None:
283                 descr._valid_consistency(self, value, context, None)
284         else:
285             if not isinstance(value, list):
286                 raise ValueError(_("invalid value {0} for option {1} "
287                                    "which must be a list").format(value,
288                                                                   self._name))
289             for index in range(0, len(value)):
290                 val = value[index]
291                 if val is not None and ((self._validator is not None and
292                                          not val_validator()) or
293                                         not self._validate(val)):
294                         raise ValueError(_("invalid value {0} for option {1}"
295                                            "").format(value, self._name))
296                 if context is not None:
297                     descr._valid_consistency(self, val, context, index)
298
299     def impl_getdefault(self, default_multi=False):
300         "accessing the default value"
301         if not default_multi or not self.impl_is_multi():
302             return self._default
303         else:
304             return self.getdefault_multi()
305
306     def impl_getdefault_multi(self):
307         "accessing the default value for a multi"
308         return self._default_multi
309
310     def impl_get_multitype(self):
311         return self._multitype
312
313     def impl_get_master_slaves(self):
314         return self._master_slaves
315
316     def impl_is_empty_by_default(self):
317         "no default value has been set yet"
318         if ((not self.impl_is_multi() and self._default is None) or
319                 (self.impl_is_multi() and (self._default == [] or None in self._default))):
320             return True
321         return False
322
323     def impl_getdoc(self):
324         "accesses the Option's doc"
325         return self.impl_get_information('doc')
326
327     def impl_has_callback(self):
328         "to know if a callback has been defined or not"
329         if self._callback is None:
330             return False
331         else:
332             return True
333
334     def impl_getkey(self, value):
335         return value
336
337     def impl_is_multi(self):
338         return self._multi
339
340     def impl_add_consistency(self, func, opt):
341         if self._consistencies is None:
342             self._consistencies = []
343         if not isinstance(opt, Option):
344             raise ValueError('consistency must be set with an option')
345         if self is opt:
346             raise ValueError('cannot add consistency with itself')
347         if self.impl_is_multi() != opt.impl_is_multi():
348             raise ValueError('options in consistency should be multi in two sides')
349         func = '_cons_{0}'.format(func)
350         self._launch_consistency(func, self, self.impl_getdefault(), None, None, opt)
351         self._consistencies.append((func, opt))
352         self.impl_validate(self.impl_getdefault())
353
354     def _cons_not_equal(self, optname, value, value_):
355         if value == value_:
356             raise ValueError(_("invalid value {0} for option {1} "
357                                "must be different as {2} option"
358                                "").format(value, self._name, optname))
359
360
361 class ChoiceOption(Option):
362     """represents a choice out of several objects.
363
364     The option can also have the value ``None``
365     """
366
367     __slots__ = ('_values', '_open_values', '_opt_type')
368     _opt_type = 'string'
369
370     def __init__(self, name, doc, values, default=None, default_multi=None,
371                  requires=None, multi=False, callback=None,
372                  callback_params=None, open_values=False, validator=None,
373                  validator_args=None, properties=()):
374         """
375         :param values: is a list of values the option can possibly take
376         """
377         if not isinstance(values, tuple):
378             raise TypeError(_('values must be a tuple for {0}').format(name))
379         self._values = values
380         if open_values not in (True, False):
381             raise TypeError(_('open_values must be a boolean for '
382                             '{0}').format(name))
383         self._open_values = open_values
384         super(ChoiceOption, self).__init__(name, doc, default=default,
385                                            default_multi=default_multi,
386                                            callback=callback,
387                                            callback_params=callback_params,
388                                            requires=requires,
389                                            multi=multi,
390                                            validator=validator,
391                                            validator_args=validator_args,
392                                            properties=properties)
393
394     def impl_get_values(self):
395         return self._values
396
397     def impl_is_openvalues(self):
398         return self._open_values
399
400     def _validate(self, value):
401         if not self._open_values:
402             return value is None or value in self._values
403         else:
404             return True
405
406
407 class BoolOption(Option):
408     "represents a choice between ``True`` and ``False``"
409     __slots__ = ('_opt_type',)
410     _opt_type = 'bool'
411
412     def _validate(self, value):
413         return isinstance(value, bool)
414
415
416 class IntOption(Option):
417     "represents a choice of an integer"
418     __slots__ = ('_opt_type',)
419     _opt_type = 'int'
420
421     def _validate(self, value):
422         return isinstance(value, int)
423
424
425 class FloatOption(Option):
426     "represents a choice of a floating point number"
427     __slots__ = ('_opt_type',)
428     _opt_type = 'float'
429
430     def _validate(self, value):
431         return isinstance(value, float)
432
433
434 class StrOption(Option):
435     "represents the choice of a string"
436     __slots__ = ('_opt_type',)
437     _opt_type = 'string'
438
439     def _validate(self, value):
440         return isinstance(value, str)
441
442
443 class UnicodeOption(Option):
444     "represents the choice of a unicode string"
445     __slots__ = ('_opt_type',)
446     _opt_type = 'unicode'
447     _empty = u''
448
449     def _validate(self, value):
450         return isinstance(value, unicode)
451
452
453 class SymLinkOption(object):
454     __slots__ = ('_name', '_opt', '_consistencies')
455     _opt_type = 'symlink'
456     _consistencies = None
457
458     def __init__(self, name, opt):
459         self._name = name
460         if not isinstance(opt, Option):
461             raise ValueError(_('malformed symlinkoption '
462                                'must be an option for symlink {0}').format(name))
463         self._opt = opt
464
465     def __getattr__(self, name):
466         if name in ('_name', '_opt', '_consistencies'):
467             return object.__gettattr__(self, name)
468         else:
469             return getattr(self._opt, name)
470
471
472 class IPOption(Option):
473     "represents the choice of an ip"
474     __slots__ = ('_opt_type', '_only_private')
475     _opt_type = 'ip'
476
477     def __init__(self, name, doc, default=None, default_multi=None,
478                  requires=None, multi=False, callback=None,
479                  callback_params=None, validator=None, validator_args=None,
480                  properties=None, only_private=False):
481         super(IPOption, self).__init__(name, doc, default=default,
482                                        default_multi=default_multi,
483                                        callback=callback,
484                                        callback_params=callback_params,
485                                        requires=requires,
486                                        multi=multi,
487                                        validator=validator,
488                                        validator_args=validator_args,
489                                        properties=properties)
490         self._only_private = only_private
491
492     def _validate(self, value):
493         try:
494             ip = IP('{0}/32'.format(value))
495             if self._only_private:
496                 return ip.iptype() == 'PRIVATE'
497             return True
498         except ValueError:
499             return False
500
501
502 class NetworkOption(Option):
503     "represents the choice of a network"
504     __slots__ = ('_opt_type',)
505     _opt_type = 'network'
506
507     def _validate(self, value):
508         try:
509             IP(value)
510             return True
511         except ValueError:
512             return False
513
514
515 class NetmaskOption(Option):
516     "represents the choice of a netmask"
517     __slots__ = ('_opt_type',)
518     _opt_type = 'netmask'
519
520     def _validate(self, value):
521         try:
522             IP('0.0.0.0/{0}'.format(value))
523             return True
524         except ValueError:
525             return False
526
527     def _cons_network_netmask(self, optname, value, value_):
528         #opts must be (netmask, network) options
529         self.__cons_netmask(optname, value, value_, False)
530
531     def _cons_ip_netmask(self, optname, value, value_):
532         #opts must be (netmask, ip) options
533         self.__cons_netmask(optname, value, value_, True)
534
535     #def __cons_netmask(self, opt, value, context, index, opts, make_net):
536     def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
537         msg = None
538         try:
539             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
540                     make_net=make_net)
541             #if cidr == 32, ip same has network
542             if ip.prefixlen() != 32:
543                 try:
544                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
545                         make_net=not make_net)
546                 except ValueError:
547                     if not make_net:
548                         msg = _("invalid network {0} ({1}) with netmask {2} ({3}),"
549                                 " this network is an ip")
550                 else:
551                     if make_net:
552                         msg = _("invalid ip {0} ({1}) with netmask {2} ({3}),"
553                                 " this ip is a network")
554
555         except ValueError:
556             if make_net:
557                 msg = _("invalid ip {0} ({1}) with netmask {2} ({3})")
558             else:
559                 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
560         if msg is not None:
561             raise ValueError(msg.format(val_ipnetwork, optname,
562                                         val_netmask, self._name))
563
564
565 class DomainnameOption(Option):
566     "represents the choice of a domain name"
567     __slots__ = ('_opt_type', '_type', '_allow_ip')
568     _opt_type = 'domainname'
569
570     def __init__(self, name, doc, default=None, default_multi=None,
571                  requires=None, multi=False, callback=None,
572                  callback_params=None, validator=None, validator_args=None,
573                  properties=None, allow_ip=False, type_='domainname'):
574         #netbios: for MS domain
575         #hostname: to identify the device
576         #domainname:
577         #fqdn: with tld, not supported yet
578         if type_ not in ['netbios', 'hostname', 'domainname']:
579             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
580         self._type = type_
581         if allow_ip not in [True, False]:
582             raise ValueError(_('allow_ip must be a boolean'))
583         self._allow_ip = allow_ip
584         super(DomainnameOption, self).__init__(name, doc, default=default,
585                                                default_multi=default_multi,
586                                                callback=callback,
587                                                callback_params=callback_params,
588                                                requires=requires,
589                                                multi=multi,
590                                                validator=validator,
591                                                validator_args=validator_args,
592                                                properties=properties)
593
594     def _validate(self, value):
595         if self._allow_ip is True:
596             try:
597                 IP('{0}/32'.format(value))
598                 return True
599             except ValueError:
600                 pass
601         if self._type == 'netbios':
602             length = 15
603             extrachar = ''
604         elif self._type == 'hostname':
605             length = 63
606             extrachar = ''
607         elif self._type == 'domainname':
608             length = 255
609             extrachar = '\.'
610             if '.' not in value:
611                 raise ValueError(_("invalid value for {0}, must have dot"
612                                    "").format(self._name))
613         if len(value) > length:
614             raise ValueError(_("invalid value's length for {0} (max {1})"
615                                "").format(self._name, length))
616         regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar)
617         return re.match(regexp, value) is not None
618
619
620 class OptionDescription(BaseInformation):
621     """Config's schema (organisation, group) and container of Options
622     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
623     """
624     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
625                  '_properties', '_children', '_consistencies')
626
627     def __init__(self, name, doc, children, requires=None, properties=None):
628         """
629         :param children: a list of options (including option descriptions)
630
631         """
632         if not valid_name(name):
633             raise ValueError(_("invalid name: {0} for option descr").format(name))
634         self._name = name
635         self._impl_informations = {}
636         self.impl_set_information('doc', doc)
637         child_names = [child._name for child in children]
638         #better performance like this
639         valid_child = copy(child_names)
640         valid_child.sort()
641         old = None
642         for child in valid_child:
643             if id(child) == id(old):
644                 raise ConflictError(_('duplicate option name: '
645                                       '{0}').format(child))
646             old = child
647         self._children = (tuple(child_names), tuple(children))
648         requires = validate_requires_arg(requires, self._name)
649         self._requires = requires
650         self._cache_paths = None
651         self._consistencies = None
652         if properties is None:
653             properties = tuple()
654         if not isinstance(properties, tuple):
655             raise TypeError(_('invalid properties type {0} for {1},'
656                               ' must be a tuple').format(type(properties), self._name))
657         self._properties = set(properties)  # 'hidden', 'disabled'...
658         # the group_type is useful for filtering OptionDescriptions in a config
659         self._group_type = groups.default
660
661     def impl_getdoc(self):
662         return self.impl_get_information('doc')
663
664     def __getattr__(self, name):
665         try:
666             return self._children[1][self._children[0].index(name)]
667         except ValueError:
668             raise AttributeError(_('unknown Option {0} in OptionDescription {1}'
669                                  '').format(name, self._name))
670
671     def impl_getkey(self, config):
672         return tuple([child.impl_getkey(getattr(config, child._name))
673                       for child in self.impl_getchildren()])
674
675     def impl_getpaths(self, include_groups=False, _currpath=None):
676         """returns a list of all paths in self, recursively
677            _currpath should not be provided (helps with recursion)
678         """
679         if _currpath is None:
680             _currpath = []
681         paths = []
682         for option in self.impl_getchildren():
683             attr = option._name
684             if isinstance(option, OptionDescription):
685                 if include_groups:
686                     paths.append('.'.join(_currpath + [attr]))
687                 paths += option.impl_getpaths(include_groups=include_groups,
688                                               _currpath=_currpath + [attr])
689             else:
690                 paths.append('.'.join(_currpath + [attr]))
691         return paths
692
693     def impl_getchildren(self):
694         return self._children[1]
695
696     def impl_build_cache(self, cache_path=None, cache_option=None, _currpath=None, _consistencies=None):
697         if _currpath is None and self._cache_paths is not None:
698             return
699         if _currpath is None:
700             save = True
701             _currpath = []
702             _consistencies = {}
703         else:
704             save = False
705         if cache_path is None:
706             cache_path = [self._name]
707             cache_option = [self]
708         for option in self.impl_getchildren():
709             attr = option._name
710             if attr.startswith('_cfgimpl'):
711                 continue
712             cache_option.append(option)
713             cache_path.append(str('.'.join(_currpath + [attr])))
714             if not isinstance(option, OptionDescription):
715                 if option._consistencies is not None:
716                     for consistency in option._consistencies:
717                         func, opt = consistency
718                         opts = (option, opt)
719                         _consistencies.setdefault(opt, []).append((func, opts))
720                         _consistencies.setdefault(option, []).append((func, opts))
721             else:
722                 _currpath.append(attr)
723                 option.impl_build_cache(cache_path, cache_option, _currpath, _consistencies)
724                 _currpath.pop()
725         if save:
726             #valid no duplicated option
727             valid_child = copy(cache_option)
728             valid_child.sort()
729             old = None
730             for child in valid_child:
731                 if id(child) == id(old):
732                     raise ConflictError(_('duplicate option: '
733                                           '{0}').format(child))
734                 old = child
735             self._cache_paths = (tuple(cache_option), tuple(cache_path))
736             self._consistencies = _consistencies
737
738     def impl_get_opt_by_path(self, path):
739         try:
740             return self._cache_paths[0][self._cache_paths[1].index(path)]
741         except ValueError:
742             raise AttributeError(_('no option for path {0}').format(path))
743
744     def impl_get_path_by_opt(self, opt):
745         try:
746             return self._cache_paths[1][self._cache_paths[0].index(opt)]
747         except ValueError:
748             raise AttributeError(_('no option {0} found').format(opt))
749
750     # ____________________________________________________________
751     def impl_set_group_type(self, group_type):
752         """sets a given group object to an OptionDescription
753
754         :param group_type: an instance of `GroupType` or `MasterGroupType`
755                               that lives in `setting.groups`
756         """
757         if self._group_type != groups.default:
758             raise TypeError(_('cannot change group_type if already set '
759                             '(old {0}, new {1})').format(self._group_type, group_type))
760         if isinstance(group_type, groups.GroupType):
761             self._group_type = group_type
762             if isinstance(group_type, groups.MasterGroupType):
763                 #if master (same name has group) is set
764                 identical_master_child_name = False
765                 #for collect all slaves
766                 slaves = []
767                 master = None
768                 for child in self.impl_getchildren():
769                     if isinstance(child, OptionDescription):
770                         raise ValueError(_("master group {0} shall not have "
771                                          "a subgroup").format(self._name))
772                     if isinstance(child, SymLinkOption):
773                         raise ValueError(_("master group {0} shall not have "
774                                          "a symlinkoption").format(self._name))
775                     if not child.impl_is_multi():
776                         raise ValueError(_("not allowed option {0} in group {1}"
777                                          ": this option is not a multi"
778                                          "").format(child._name, self._name))
779                     if child._name == self._name:
780                         identical_master_child_name = True
781                         child._multitype = multitypes.master
782                         master = child
783                     else:
784                         slaves.append(child)
785                 if master is None:
786                     raise ValueError(_('master group with wrong master name for {0}'
787                                      '').format(self._name))
788                 master._master_slaves = tuple(slaves)
789                 for child in self.impl_getchildren():
790                     if child != master:
791                         child._master_slaves = master
792                         child._multitype = multitypes.slave
793                 if not identical_master_child_name:
794                     raise ValueError(_("the master group: {0} has not any "
795                                      "master child").format(self._name))
796         else:
797             raise ValueError(_('not allowed group_type : {0}').format(group_type))
798
799     def impl_get_group_type(self):
800         return self._group_type
801
802     def _valid_consistency(self, opt, value, context=None, index=None):
803         consistencies = self._consistencies.get(opt)
804         if consistencies is not None:
805             for consistency in consistencies:
806                 opt_ = consistency[1]
807                 ret = opt_[0]._launch_consistency(consistency[0], opt, value, context,
808                                                   index, opt_[1])
809                 if ret is False:
810                     return False
811         return True
812
813
814 def validate_requires_arg(requires, name):
815     """check malformed requirements
816     and tranform dict to internal tuple
817     """
818     if requires is None:
819         return None
820     ret_requires = {}
821     config_action = {}
822
823     for require in requires:
824         if not type(require) == dict:
825             raise ValueError(_("malformed requirements type for option:"
826                                " {0}, must be a dict").format(name))
827         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
828                       'same_action')
829         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
830         if unknown_keys != frozenset():
831             raise ValueError('malformed requirements for option: {0}'
832                              ' unknown keys {1}, must only '
833                              '{2}'.format(name,
834                                           unknown_keys,
835                                           valid_keys))
836         try:
837             option = require['option']
838             expected = require['expected']
839             action = require['action']
840         except KeyError:
841             raise ValueError(_("malformed requirements for option: {0}"
842                                " require must have option, expected and"
843                                " action keys").format(name))
844         inverse = require.get('inverse', False)
845         if inverse not in [True, False]:
846             raise ValueError(_('malformed requirements for option: {0}'
847                                ' inverse must be boolean'))
848         transitive = require.get('transitive', True)
849         if transitive not in [True, False]:
850             raise ValueError(_('malformed requirements for option: {0}'
851                                ' transitive must be boolean'))
852         same_action = require.get('same_action', True)
853         if same_action not in [True, False]:
854             raise ValueError(_('malformed requirements for option: {0}'
855                                ' same_action must be boolean'))
856
857         if not isinstance(option, Option):
858             raise ValueError(_('malformed requirements first argument '
859                                'must be an option in option {0}').format(name))
860         if option.impl_is_multi():
861             raise ValueError(_('malformed requirements option {0} '
862                                'should not be a multi').format(name))
863         if expected is not None and not option._validate(expected):
864             raise ValueError(_('malformed requirements second argument '
865                                'must be valid for option {0}').format(name))
866         if action in config_action:
867             if inverse != config_action[action]:
868                 raise ValueError(_("inconsistency in action types for option: {0}"
869                                    " action: {1}").format(name, action))
870         else:
871             config_action[action] = inverse
872         if action not in ret_requires:
873             ret_requires[action] = {}
874         if option not in ret_requires[action]:
875             ret_requires[action][option] = (option, [expected], action,
876                                             inverse, transitive, same_action)
877         else:
878             ret_requires[action][option][1].append(expected)
879
880     ret = []
881     for opt_requires in ret_requires.values():
882         ret_action = []
883         for require in opt_requires.values():
884             req = (require[0], tuple(require[1]), require[2], require[3], require[4], require[5])
885             ret_action.append(req)
886         ret.append(tuple(ret_action))
887     return tuple(ret)