1 # -*- coding: utf-8 -*-
2 "option types and option description for the configuration management"
3 # Copyright (C) 2012 Team tiramisu (see README for all contributors)
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.
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.
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
19 # The original `Config` design model is unproudly borrowed from
20 # the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
23 from autolib import special_owners
24 from basetype import HiddenBaseType, DisabledBaseType, ModeBaseType, modes
25 from error import (ConfigError, ConflictConfigError, NotFoundError,
27 available_actions = ['hide', 'show', 'enable', 'disable']
28 reverse_actions = {'hide': 'show', 'show': 'hide',
29 'disable':'enable', 'enable': 'disable'}
30 # ____________________________________________________________
31 # OptionDescription authorized group_type values
32 group_types = ['default', 'family', 'group', 'master']
33 # ____________________________________________________________
34 class Option(HiddenBaseType, DisabledBaseType, ModeBaseType):
35 #reminder: an Option object is **not** a container for the value
37 def __init__(self, name, doc, default=None, requires=None,
38 mandatory=False, multi=False, callback=None, mode='normal'):
41 self._requires = requires
42 self._mandatory = mandatory
44 self.callback = callback
46 raise ConfigError("mode {0} not available".format(mode))
49 if not self.validate(default):
50 raise ConfigError("invalid default value {0} "
51 "for option {1}".format(default, name))
52 self.default = default
54 def validate(self, value):
55 raise NotImplementedError('abstract base class')
63 def getcallback(self):
66 def setowner(self, config, who):
69 raise TypeError("trying to change a frozen option's owner: %s" % name)
70 if who in special_owners:
71 if self.callback == None:
72 raise SpecialOwnersError("no callback specified for"
73 "option {0}".format(name))
74 config._cfgimpl_value_owners[name] = who
76 def setoption(self, config, value, who):
79 raise TypeError('trying to change a frozen option object: %s' % name)
80 # we want the possibility to reset everything
81 if who == "default" and value is None:
84 if not self.validate(value):
85 raise ConfigError('invalid value %s for option %s' % (value, name))
87 # changes the default value (and therefore resets the previous value)
89 apply_requires(self, config)
90 # FIXME put the validation for the multi somewhere else
91 # # it is a multi **and** it has requires
92 # if self.multi == True:
93 # if type(value) != list:
94 # raise TypeError("value {0} must be a list".format(value))
95 # if self._requires is not None:
96 # for reqname in self._requires:
97 # # FIXME : verify that the slaves are all multi
98 # #option = getattr(config._cfgimpl_descr, reqname)
99 # # if not option.multi == True:
100 # # raise ConflictConfigError("an option with requires "
101 # # "has to be a list type : {0}".format(name))
102 # if len(config._cfgimpl_values[reqname]) != len(value):
103 # raise ConflictConfigError("an option with requires "
104 # "has not the same length of the others "
105 # "in the group : {0}".format(reqname))
106 config._cfgimpl_previous_values[name] = config._cfgimpl_values[name]
107 config._cfgimpl_values[name] = value
109 def getkey(self, value):
118 # ____________________________________________________________
122 def is_mandatory(self):
123 return self._mandatory
125 class ChoiceOption(Option):
128 def __init__(self, name, doc, values, default=None, requires=None,
129 multi=False, mandatory=False):
131 super(ChoiceOption, self).__init__(name, doc, default=default,
132 requires=requires, multi=multi, mandatory=mandatory)
134 def setoption(self, config, value, who):
136 super(ChoiceOption, self).setoption(config, value, who)
138 def validate(self, value):
139 if self.multi == False:
140 return value is None or value in self.values
143 if not (val is None or val in self.values):
147 class BoolOption(Option):
150 def __init__(self, *args, **kwargs):
151 super(BoolOption, self).__init__(*args, **kwargs)
152 # def __init__(self, name, doc, default=None, requires=None,
153 # validator=None, multi=False, mandatory=False):
154 # super(BoolOption, self).__init__(name, doc, default=default,
155 # requires=requires, multi=multi, mandatory=mandatory)
156 #self._validator = validator
158 def validate(self, value):
159 if self.multi == False:
160 return isinstance(value, bool)
164 if not isinstance(val, bool):
169 # FIXME config level validator
170 # def setoption(self, config, value, who):
172 # if value and self._validator is not None:
173 # toplevel = config._cfgimpl_get_toplevel()
174 # self._validator(toplevel)
175 # super(BoolOption, self).setoption(config, value, who)
177 class IntOption(Option):
180 def __init__(self, *args, **kwargs):
181 super(IntOption, self).__init__(*args, **kwargs)
183 def validate(self, value):
184 if self.multi == False:
198 def setoption(self, config, value, who):
200 super(IntOption, self).setoption(config, value, who)
202 raise ConfigError(*e.args)
204 class FloatOption(Option):
207 def __init__(self, *args, **kwargs):
208 super(FloatOption, self).__init__(*args, **kwargs)
210 def validate(self, value):
211 if self.multi == False:
225 def setoption(self, config, value, who):
227 super(FloatOption, self).setoption(config, float(value), who)
229 raise ConfigError(*e.args)
231 class StrOption(Option):
234 def __init__(self, *args, **kwargs):
235 super(StrOption, self).__init__(*args, **kwargs)
237 def validate(self, value):
238 if self.multi == False:
239 return isinstance(value, str)
242 if not isinstance(val, str):
247 def setoption(self, config, value, who):
249 super(StrOption, self).setoption(config, value, who)
251 raise ConfigError(*e.args)
253 class SymLinkOption(object): #(HiddenBaseType, DisabledBaseType):
256 def __init__(self, name, path):
260 def setoption(self, config, value, who):
262 setattr(config, self.path, value) # .setoption(self.path, value, who)
264 raise ConfigError(*e.args)
266 class IPOption(Option):
269 def __init__(self, *args, **kwargs):
270 super(IPOption, self).__init__(*args, **kwargs)
272 def validate(self, value):
273 # by now the validation is nothing but a string, use IPy instead
274 if self.multi == False:
275 return isinstance(value, str)
278 if not isinstance(val, str):
283 def setoption(self, config, value, who):
285 super(IPOption, self).setoption(config, value, who)
287 raise ConfigError(*e.args)
289 class NetmaskOption(Option):
292 def __init__(self, *args, **kwargs):
293 super(NetmaskOption, self).__init__(*args, **kwargs)
295 def validate(self, value):
296 # by now the validation is nothing but a string, use IPy instead
297 if self.multi == False:
298 return isinstance(value, str)
301 if not isinstance(val, str):
306 def setoption(self, config, value, who):
308 super(NetmaskOption, self).setoption(config, value, who)
310 raise ConfigError(*e.args)
312 class ArbitraryOption(Option):
313 def __init__(self, name, doc, default=None, defaultfactory=None,
314 requires=None, multi=False, mandatory=False):
315 super(ArbitraryOption, self).__init__(name, doc, requires=requires,
316 multi=multi, mandatory=mandatory)
317 self.defaultfactory = defaultfactory
318 if defaultfactory is not None:
319 assert default is None
321 def validate(self, value):
324 def getdefault(self):
325 if self.defaultfactory is not None:
326 return self.defaultfactory()
329 class OptionDescription(HiddenBaseType, DisabledBaseType, ModeBaseType):
330 group_type = 'default'
332 def __init__(self, name, doc, children, requires=None):
335 self._children = children
336 self._requires = requires
343 for child in self._children:
344 setattr(self, child._name, child)
346 def add_child(self, child):
347 "dynamically adds a configuration option"
348 #Nothing is static. Even the Mona Lisa is falling apart.
349 for ch in self._children:
350 if isinstance(ch, Option):
351 if child._name == ch._name:
352 raise ConflictConfigError("existing option : {0}".format(
354 self._children.append(child)
355 setattr(self, child._name, child)
357 def update_child(self, child):
358 "modification of an existing option"
359 # XXX : corresponds to the `redefine`, is it usefull
362 def getkey(self, config):
363 return tuple([child.getkey(getattr(config, child._name))
364 for child in self._children])
366 def getpaths(self, include_groups=False, currpath=None):
367 """returns a list of all paths in self, recursively
368 currpath should not be provided (helps with recursion)
373 for option in self._children:
375 if attr.startswith('_cfgimpl'):
377 value = getattr(self, attr)
378 if isinstance(value, OptionDescription):
380 paths.append('.'.join(currpath + [attr]))
381 currpath.append(attr)
382 paths += value.getpaths(include_groups=include_groups,
386 paths.append('.'.join(currpath + [attr]))
388 # ____________________________________________________________
390 def set_group_type(self, group_type):
391 if group_type in group_types:
392 self.group_type = group_type
394 raise ConfigError('not allowed value for group_type : {0}'.format(
397 def get_group_type(self):
398 return self.group_type
399 # ____________________________________________________________
401 super(OptionDescription, self).hide()
402 # FIXME : AND THE SUBCHILDREN ?
403 for child in self._children:
404 if isinstance(child, OptionDescription):
408 # FIXME : AND THE SUBCHILDREN ??
409 super(OptionDescription, self).show()
410 for child in self._children:
411 if isinstance(child, OptionDescription):
413 # ____________________________________________________________
415 super(OptionDescription, self).disable()
416 # FIXME : AND THE SUBCHILDREN ?
417 for child in self._children:
418 if isinstance(child, OptionDescription):
422 # FIXME : AND THE SUBCHILDREN ?
423 super(OptionDescription, self).enable()
424 for child in self._children:
425 if isinstance(child, OptionDescription):
427 # ____________________________________________________________
428 def apply_requires(opt, config):
429 if hasattr(opt, '_requires'):
430 if opt._requires is not None:
431 # malformed requirements
432 rootconfig = config._cfgimpl_get_toplevel()
433 for req in opt._requires:
434 if not type(req) == tuple and len(req) in (3, 4):
435 raise RequiresError("malformed requirements for option:"
436 " {0}".format(opt._name))
437 # all actions **must** be identical
438 actions = [req[2] for req in opt._requires]
442 raise RequiresError("malformed requirements for option:"
443 " {0}".format(opt._name))
444 # filters the callbacks
446 for req in opt._requires:
448 name, expected, action = req
451 name, expected, action, inverted = req
452 if inverted == 'inverted':
454 homeconfig, shortname = \
455 rootconfig._cfgimpl_get_home_by_path(name)
456 # FIXME: doesn't work with 'auto' or 'fill' yet
457 # (copy the code from the __getattr__
458 if shortname in homeconfig._cfgimpl_values:
459 value = homeconfig._cfgimpl_values[shortname]
460 if (not inverted and value == expected) or \
461 (inverted and value != expected):
462 if action not in available_actions:
463 raise RequiresError("malformed requirements"
464 " for option: {0}".format(opt._name))
465 getattr(opt, action)() #.hide() or show() or...
467 else: # option doesn't exist ! should not happen...
468 raise NotFoundError("required option not found: "
470 # no callback has been triggered, then just reverse the action
472 getattr(opt, reverse_actions[action])()