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