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