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