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