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