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