add sqlite plugin
[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} 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} 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(_("default value 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__.__name__))
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, not '
442                                '{0}').format(type(value)))
443
444
445 class UnicodeOption(Option):
446     "represents the choice of a unicode string"
447     __slots__ = ('_opt_type',)
448     _opt_type = 'unicode'
449     _empty = u''
450
451     def _validate(self, value):
452         if not isinstance(value, unicode):
453             raise ValueError(_('value must be an unicode'))
454
455
456 class SymLinkOption(object):
457     __slots__ = ('_name', '_opt', '_consistencies')
458     _opt_type = 'symlink'
459     _consistencies = None
460
461     def __init__(self, name, opt):
462         self._name = name
463         if not isinstance(opt, Option):
464             raise ValueError(_('malformed symlinkoption '
465                                'must be an option for symlink {0}').format(name))
466         self._opt = opt
467
468     def __getattr__(self, name):
469         if name in ('_name', '_opt', '_consistencies'):
470             return object.__gettattr__(self, name)
471         else:
472             return getattr(self._opt, name)
473
474
475 class IPOption(Option):
476     "represents the choice of an ip"
477     __slots__ = ('_opt_type', '_only_private')
478     _opt_type = 'ip'
479
480     def __init__(self, name, doc, default=None, default_multi=None,
481                  requires=None, multi=False, callback=None,
482                  callback_params=None, validator=None, validator_args=None,
483                  properties=None, only_private=False):
484         self._only_private = only_private
485         super(IPOption, self).__init__(name, doc, default=default,
486                                        default_multi=default_multi,
487                                        callback=callback,
488                                        callback_params=callback_params,
489                                        requires=requires,
490                                        multi=multi,
491                                        validator=validator,
492                                        validator_args=validator_args,
493                                        properties=properties)
494
495     def _validate(self, value):
496         ip = IP('{0}/32'.format(value))
497         if ip.iptype() == 'RESERVED':
498             raise ValueError(_("IP mustn't not be in reserved class"))
499         if self._only_private and not ip.iptype() == 'PRIVATE':
500             raise ValueError(_("IP must be in private class"))
501
502
503 class PortOption(Option):
504     """represents the choice of a port
505     The port numbers are divided into three ranges:
506     the well-known ports,
507     the registered ports,
508     and the dynamic or private ports.
509     You can actived this three range.
510     Port number 0 is reserved and can't be used.
511     see: http://en.wikipedia.org/wiki/Port_numbers
512     """
513     __slots__ = ('_opt_type', '_allow_range', '_allow_zero', '_min_value',
514                  '_max_value')
515     _opt_type = 'port'
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_range=False, allow_zero=False,
521                  allow_wellknown=True, allow_registred=True,
522                  allow_private=False):
523         self._allow_range = allow_range
524         self._min_value = None
525         self._max_value = None
526         ports_min = [0, 1, 1024, 49152]
527         ports_max = [0, 1023, 49151, 65535]
528         is_finally = False
529         for index, allowed in enumerate([allow_zero, allow_wellknown, allow_registred, allow_private]):
530             if self._min_value is None:
531                 if allowed:
532                     self._min_value = ports_min[index]
533             elif not allowed:
534                 is_finally = True
535             elif allowed and is_finally:
536                 raise ValueError(_('inconsistency in allowed range'))
537             if allowed:
538                 self._max_value = ports_max[index]
539
540         if self._max_value is None:
541             raise ValueError(_('max value is empty'))
542
543         super(PortOption, self).__init__(name, doc, default=default,
544                                          default_multi=default_multi,
545                                          callback=callback,
546                                          callback_params=callback_params,
547                                          requires=requires,
548                                          multi=multi,
549                                          validator=validator,
550                                          validator_args=validator_args,
551                                          properties=properties)
552
553     def _validate(self, value):
554         if self._allow_range and ":" in str(value):
555             value = str(value).split(':')
556             if len(value) != 2:
557                 raise ValueError('range must have two values only')
558             if not value[0] < value[1]:
559                 raise ValueError('first port in range must be smaller than the second one')
560         else:
561             value = [value]
562
563         for val in value:
564             if not self._min_value <= int(val) <= self._max_value:
565                 raise ValueError('port must be an between {0} and {1}'
566                                  ''.format(self._min_value, self._max_value))
567
568
569 class NetworkOption(Option):
570     "represents the choice of a network"
571     __slots__ = ('_opt_type',)
572     _opt_type = 'network'
573
574     def _validate(self, value):
575         ip = IP(value)
576         if ip.iptype() == 'RESERVED':
577             raise ValueError(_("network mustn't not be in reserved class"))
578
579
580 class NetmaskOption(Option):
581     "represents the choice of a netmask"
582     __slots__ = ('_opt_type',)
583     _opt_type = 'netmask'
584
585     def _validate(self, value):
586         IP('0.0.0.0/{0}'.format(value))
587
588     def _cons_network_netmask(self, optname, value, value_):
589         #opts must be (netmask, network) options
590         self.__cons_netmask(optname, value, value_, False)
591
592     def _cons_ip_netmask(self, optname, value, value_):
593         #opts must be (netmask, ip) options
594         self.__cons_netmask(optname, value, value_, True)
595
596     #def __cons_netmask(self, opt, value, context, index, opts, make_net):
597     def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
598         msg = None
599         try:
600             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
601                     make_net=make_net)
602             #if cidr == 32, ip same has network
603             if ip.prefixlen() != 32:
604                 try:
605                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
606                         make_net=not make_net)
607                 except ValueError:
608                     if not make_net:
609                         msg = _("invalid network {0} ({1}) with netmask {2} ({3}),"
610                                 " this network is an IP")
611                 else:
612                     if make_net:
613                         msg = _("invalid IP {0} ({1}) with netmask {2} ({3}),"
614                                 " this IP is a network")
615
616         except ValueError:
617             if make_net:
618                 msg = _("invalid IP {0} ({1}) with netmask {2} ({3})")
619             else:
620                 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
621         if msg is not None:
622             raise ValueError(msg.format(val_ipnetwork, optname,
623                                         val_netmask, self._name))
624
625
626 class DomainnameOption(Option):
627     "represents the choice of a domain name"
628     __slots__ = ('_opt_type', '_type', '_allow_ip')
629     _opt_type = 'domainname'
630
631     def __init__(self, name, doc, default=None, default_multi=None,
632                  requires=None, multi=False, callback=None,
633                  callback_params=None, validator=None, validator_args=None,
634                  properties=None, allow_ip=False, type_='domainname'):
635         #netbios: for MS domain
636         #hostname: to identify the device
637         #domainname:
638         #fqdn: with tld, not supported yet
639         if type_ not in ['netbios', 'hostname', 'domainname']:
640             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
641         self._type = type_
642         if allow_ip not in [True, False]:
643             raise ValueError(_('allow_ip must be a boolean'))
644         self._allow_ip = allow_ip
645         super(DomainnameOption, self).__init__(name, doc, default=default,
646                                                default_multi=default_multi,
647                                                callback=callback,
648                                                callback_params=callback_params,
649                                                requires=requires,
650                                                multi=multi,
651                                                validator=validator,
652                                                validator_args=validator_args,
653                                                properties=properties)
654
655     def _validate(self, value):
656         if self._allow_ip is True:
657             try:
658                 IP('{0}/32'.format(value))
659                 return
660             except ValueError:
661                 pass
662         if self._type == 'netbios':
663             length = 15
664             extrachar = ''
665         elif self._type == 'hostname':
666             length = 63
667             extrachar = ''
668         elif self._type == 'domainname':
669             length = 255
670             extrachar = '\.'
671             if '.' not in value:
672                 raise ValueError(_("invalid value for {0}, must have dot"
673                                    "").format(self._name))
674         if len(value) > length:
675             raise ValueError(_("invalid domainname's length for {0} (max {1})").format(self._name, length))
676         if len(value) == 1:
677             raise ValueError(_("invalid domainname's length for {0} (min 2)"
678                                "").format(self._name))
679         regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar)
680         if re.match(regexp, value) is None:
681             raise ValueError(_('invalid domainname'))
682
683
684 class OptionDescription(BaseInformation):
685     """Config's schema (organisation, group) and container of Options
686     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
687     """
688     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
689                  '_properties', '_children', '_consistencies')
690
691     def __init__(self, name, doc, children, requires=None, properties=None):
692         """
693         :param children: a list of options (including optiondescriptions)
694
695         """
696         if not valid_name(name):
697             raise ValueError(_("invalid name: {0} for optiondescription").format(name))
698         self._name = name
699         self._impl_informations = {}
700         self.impl_set_information('doc', doc)
701         child_names = [child._name for child in children]
702         #better performance like this
703         valid_child = copy(child_names)
704         valid_child.sort()
705         old = None
706         for child in valid_child:
707             if id(child) == id(old):
708                 raise ConflictError(_('duplicate option name: '
709                                       '{0}').format(child))
710             old = child
711         self._children = (tuple(child_names), tuple(children))
712         requires = validate_requires_arg(requires, self._name)
713         self._requires = requires
714         self._cache_paths = None
715         self._consistencies = None
716         if properties is None:
717             properties = tuple()
718         if not isinstance(properties, tuple):
719             raise TypeError(_('invalid properties type {0} for {1},'
720                               ' must be a tuple').format(type(properties), self._name))
721         self._properties = properties  # 'hidden', 'disabled'...
722         # the group_type is useful for filtering OptionDescriptions in a config
723         self._group_type = groups.default
724
725     def impl_getdoc(self):
726         return self.impl_get_information('doc')
727
728     def __getattr__(self, name):
729         try:
730             return self._children[1][self._children[0].index(name)]
731         except ValueError:
732             raise AttributeError(_('unknown Option {0} in OptionDescription {1}'
733                                  '').format(name, self._name))
734
735     def impl_getkey(self, config):
736         return tuple([child.impl_getkey(getattr(config, child._name))
737                       for child in self.impl_getchildren()])
738
739     def impl_getpaths(self, include_groups=False, _currpath=None):
740         """returns a list of all paths in self, recursively
741            _currpath should not be provided (helps with recursion)
742         """
743         if _currpath is None:
744             _currpath = []
745         paths = []
746         for option in self.impl_getchildren():
747             attr = option._name
748             if isinstance(option, OptionDescription):
749                 if include_groups:
750                     paths.append('.'.join(_currpath + [attr]))
751                 paths += option.impl_getpaths(include_groups=include_groups,
752                                               _currpath=_currpath + [attr])
753             else:
754                 paths.append('.'.join(_currpath + [attr]))
755         return paths
756
757     def impl_getchildren(self):
758         return self._children[1]
759
760     def impl_build_cache(self, cache_path=None, cache_option=None, _currpath=None, _consistencies=None):
761         if _currpath is None and self._cache_paths is not None:
762             return
763         if _currpath is None:
764             save = True
765             _currpath = []
766             _consistencies = {}
767         else:
768             save = False
769         if cache_path is None:
770             cache_path = [self._name]
771             cache_option = [self]
772         for option in self.impl_getchildren():
773             attr = option._name
774             if attr.startswith('_cfgimpl'):
775                 continue
776             cache_option.append(option)
777             cache_path.append(str('.'.join(_currpath + [attr])))
778             if not isinstance(option, OptionDescription):
779                 if option._consistencies is not None:
780                     for consistency in option._consistencies:
781                         func, opt = consistency
782                         opts = (option, opt)
783                         _consistencies.setdefault(opt, []).append((func, opts))
784                         _consistencies.setdefault(option, []).append((func, opts))
785             else:
786                 _currpath.append(attr)
787                 option.impl_build_cache(cache_path, cache_option, _currpath, _consistencies)
788                 _currpath.pop()
789         if save:
790             #valid no duplicated option
791             valid_child = copy(cache_option)
792             valid_child.sort()
793             old = None
794             for child in valid_child:
795                 if id(child) == id(old):
796                     raise ConflictError(_('duplicate option: '
797                                           '{0}').format(child))
798                 old = child
799             self._cache_paths = (tuple(cache_option), tuple(cache_path))
800             self._consistencies = _consistencies
801
802     def impl_get_opt_by_path(self, path):
803         try:
804             return self._cache_paths[0][self._cache_paths[1].index(path)]
805         except ValueError:
806             raise AttributeError(_('no option for path {0}').format(path))
807
808     def impl_get_path_by_opt(self, opt):
809         try:
810             return self._cache_paths[1][self._cache_paths[0].index(opt)]
811         except ValueError:
812             raise AttributeError(_('no option {0} found').format(opt))
813
814     # ____________________________________________________________
815     def impl_set_group_type(self, group_type):
816         """sets a given group object to an OptionDescription
817
818         :param group_type: an instance of `GroupType` or `MasterGroupType`
819                               that lives in `setting.groups`
820         """
821         if self._group_type != groups.default:
822             raise TypeError(_('cannot change group_type if already set '
823                             '(old {0}, new {1})').format(self._group_type, group_type))
824         if isinstance(group_type, groups.GroupType):
825             self._group_type = group_type
826             if isinstance(group_type, groups.MasterGroupType):
827                 #if master (same name has group) is set
828                 identical_master_child_name = False
829                 #for collect all slaves
830                 slaves = []
831                 master = None
832                 for child in self.impl_getchildren():
833                     if isinstance(child, OptionDescription):
834                         raise ValueError(_("master group {0} shall not have "
835                                          "a subgroup").format(self._name))
836                     if isinstance(child, SymLinkOption):
837                         raise ValueError(_("master group {0} shall not have "
838                                          "a symlinkoption").format(self._name))
839                     if not child.impl_is_multi():
840                         raise ValueError(_("not allowed option {0} in group {1}"
841                                          ": this option is not a multi"
842                                          "").format(child._name, self._name))
843                     if child._name == self._name:
844                         identical_master_child_name = True
845                         child._multitype = multitypes.master
846                         master = child
847                     else:
848                         slaves.append(child)
849                 if master is None:
850                     raise ValueError(_('master group with wrong master name for {0}'
851                                      '').format(self._name))
852                 master._master_slaves = tuple(slaves)
853                 for child in self.impl_getchildren():
854                     if child != master:
855                         child._master_slaves = master
856                         child._multitype = multitypes.slave
857                 if not identical_master_child_name:
858                     raise ValueError(_("no child has same nom has master group "
859                                        "for: {0}").format(self._name))
860         else:
861             raise ValueError(_('not allowed group_type : {0}').format(group_type))
862
863     def impl_get_group_type(self):
864         return self._group_type
865
866     def _valid_consistency(self, opt, value, context=None, index=None):
867         consistencies = self._consistencies.get(opt)
868         if consistencies is not None:
869             for consistency in consistencies:
870                 opt_ = consistency[1]
871                 ret = opt_[0]._launch_consistency(consistency[0], opt, value, context,
872                                                   index, opt_[1])
873                 if ret is False:
874                     return False
875         return True
876
877
878 def validate_requires_arg(requires, name):
879     """check malformed requirements
880     and tranform dict to internal tuple
881     """
882     if requires is None:
883         return None
884     ret_requires = {}
885     config_action = {}
886
887     for require in requires:
888         if not type(require) == dict:
889             print require
890             raise ValueError(_("malformed requirements type for option:"
891                                " {0}, must be a dict").format(name))
892         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
893                       'same_action')
894         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
895         if unknown_keys != frozenset():
896             raise ValueError('malformed requirements for option: {0}'
897                              ' unknown keys {1}, must only '
898                              '{2}'.format(name,
899                                           unknown_keys,
900                                           valid_keys))
901         try:
902             option = require['option']
903             expected = require['expected']
904             action = require['action']
905         except KeyError:
906             raise ValueError(_("malformed requirements for option: {0}"
907                                " require must have option, expected and"
908                                " action keys").format(name))
909         inverse = require.get('inverse', False)
910         if inverse not in [True, False]:
911             raise ValueError(_('malformed requirements for option: {0}'
912                                ' inverse must be boolean'))
913         transitive = require.get('transitive', True)
914         if transitive not in [True, False]:
915             raise ValueError(_('malformed requirements for option: {0}'
916                                ' transitive must be boolean'))
917         same_action = require.get('same_action', True)
918         if same_action not in [True, False]:
919             raise ValueError(_('malformed requirements for option: {0}'
920                                ' same_action must be boolean'))
921
922         if not isinstance(option, Option):
923             print option, type(option)
924             raise ValueError(_('malformed requirements '
925                                'must be an option in option {0}').format(name))
926         if option.impl_is_multi():
927             raise ValueError(_('malformed requirements option {0} '
928                                'should not be a multi').format(name))
929         if expected is not None:
930             try:
931                 option._validate(expected)
932             except ValueError, err:
933                 raise ValueError(_('malformed requirements second argument '
934                                    'must be valid for option {0}: {1}').format(name, err))
935         if action in config_action:
936             if inverse != config_action[action]:
937                 raise ValueError(_("inconsistency in action types for option: {0}"
938                                    " action: {1}").format(name, action))
939         else:
940             config_action[action] = inverse
941         if action not in ret_requires:
942             ret_requires[action] = {}
943         if option not in ret_requires[action]:
944             ret_requires[action][option] = (option, [expected], action,
945                                             inverse, transitive, same_action)
946         else:
947             ret_requires[action][option][1].append(expected)
948
949     ret = []
950     for opt_requires in ret_requires.values():
951         ret_action = []
952         for require in opt_requires.values():
953             req = (require[0], tuple(require[1]), require[2], require[3], require[4], require[5])
954             ret_action.append(req)
955         ret.append(tuple(ret_action))
956     return tuple(ret)