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