diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 55b655e..44b8934 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -85,7 +85,12 @@ def carry_out_calculation(name, option, config): params.append(value) else: tcp[key] = value - ret.append(calculate(name, callback, params, tcp)) + calc = calculate(name, callback, params, tcp) + if isinstance(calc, list): + ret.extend(calc) + else: + ret.append(calc) + return ret else: tcp = {} diff --git a/tiramisu/config.py b/tiramisu/config.py index 5331c1e..13fb6e9 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -25,8 +25,8 @@ from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError, AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, MandatoryError, MethodCallError, NoValueReturned) from tiramisu.option import (OptionDescription, Option, SymLinkOption, - group_types, Multi, apply_requires) -from tiramisu.setting import settings + Multi, apply_requires) +from tiramisu.setting import settings, group_types # ____________________________________________________________ class Config(object): @@ -159,6 +159,52 @@ class Config(object): def __getattr__(self, name): return self._getattr(name) + def _get_master_len(self, slave_name): + try: + parent_cfg = self._cfgimpl_parent + if parent_cfg is None: + return None + master_name = parent_cfg._cfgimpl_descr.get_master_name() + master_value = getattr(parent_cfg, master_name) + return len(master_value) + except TypeError: + # in this case we just don't care about the len + return None + + def _valid_len(self, slave_name, slave_value): + master_len = self._get_master_len(slave_name) + if master_len == None: + return True + if master_len != len(slave_value): + raise ValueError("invalid len for the group {0}" + "in the option {1} ".format(master_name, slave_name)) + + def fill_multi(self, name, result, default_multi=None): + """fills a multi option with default and calculated values + """ + value = self._cfgimpl_values[name] + master_len = self._get_master_len(name) + if not isinstance(result, list): + if master_len is None: + master_len = 1 + # a list is built with the same len as the master + _result = [] + for i in range(master_len): + _result.append(result) + elif default_multi != None: + if master_len != None: + slave_len = len(result) + if slave_len > master_len: + raise ValueError("invalid value's len for" + "the option: {1}".format(name)) + if slave_len != master_len: + delta_len = master_len - len(result) + for i in range(delta_len): + _result.append(default_multi) + else: + _result = result + return Multi(_result, value.config, value.child) + def _getattr(self, name, permissive=False): """ attribute notation mechanism for accessing the value of an option @@ -195,6 +241,7 @@ class Config(object): if (not opt_or_descr.is_frozen() or \ not opt_or_descr.is_forced_on_freeze()) and \ not opt_or_descr.is_default_owner(self): + self._valid_len(name, value) return value try: result = opt_or_descr.getcallback_value( @@ -203,9 +250,7 @@ class Config(object): pass else: if opt_or_descr.is_multi(): - if not isinstance(result, list): - result = [result] - _result = Multi(result, value.config, value.child) + _result = self.fill_multi(name, result) else: # this result **shall not** be a list if isinstance(result, list): @@ -218,11 +263,17 @@ class Config(object): ' for option {0}'.format(name)) self._cfgimpl_values[name] = _result opt_or_descr.setowner(self, 'default') - if not opt_or_descr.has_callback() and opt_or_descr.is_forced_on_freeze(): - return opt_or_descr.getdefault() - self._test_mandatory(name, opt_or_descr) # frozen and force default - return self._cfgimpl_values[name] + if not opt_or_descr.has_callback() and opt_or_descr.is_forced_on_freeze(): + value = opt_or_descr.getdefault() + if opt_or_descr.is_multi(): + value = self.fill_multi(name, result, opt_or_descr.getdefault_multi()) + self._cfgimpl_values[name] = value + opt_or_descr.setowner(self, 'default') + self._test_mandatory(name, opt_or_descr) + value = self._cfgimpl_values[name] + self._valid_len(name, value) + return value def unwrap_from_name(self, name): """convenience method to extract and Option() object from the Config() @@ -375,10 +426,8 @@ class Config(object): def __eq__(self, other): "Config comparison" - if not isinstance(other, Config): + if not isinstance(other, OptionDescription): return False - print self.getkey() - print other.getkey() return self.getkey() == other.getkey() def __ne__(self, other): diff --git a/tiramisu/option.py b/tiramisu/option.py index 1af3a81..007bf84 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -26,7 +26,7 @@ from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError, RequiresError, RequirementRecursionError, MandatoryError, PropertiesOptionError) from tiramisu.autolib import carry_out_calculation -from tiramisu.setting import settings +from tiramisu.setting import settings, group_types, groups_has_master requires_actions = [('hide', 'show'), ('enable', 'disable'), ('freeze', 'unfreeze')] @@ -37,15 +37,6 @@ for act1, act2 in requires_actions: reverse_actions[act1] = act2 reverse_actions[act2] = act1 # ____________________________________________________________ -# OptionDescription authorized group_type values -""" -Three available group_types : `default`, `family`, `group` and -`master` (for master~slave group type). Notice that for a -master~slave group, the name of the group and the name of the -master option are identical. -""" -group_types = ['default', 'family', 'group', 'master'] -# ____________________________________________________________ # multi types class Multi(list): @@ -416,6 +407,9 @@ class OptionDescription(HiddenBaseType, DisabledBaseType): self._requires = requires self._build() self.properties = [] # 'hidden', 'disabled'... + # if this group is a master group, master is set + # to the master option name + self.master = None def getdoc(self): return self.doc @@ -466,16 +460,33 @@ class OptionDescription(HiddenBaseType, DisabledBaseType): paths.append('.'.join(currpath + [attr])) return paths # ____________________________________________________________ - def set_group_type(self, group_type): + def set_group_type(self, group_type, master=None): ":param group_type: string in group_types" if group_type in group_types: self.group_type = group_type + if group_type in groups_has_master: + if master is None: + raise ConfigError('this group type ({0}) needs a master ' + 'for OptionDescription {1}'.format(group_type, + self._name)) + else: + if master is not None: + raise ConfigError("this group type ({0}) doesn't need a " + "master for OptionDescription {1}".format( + group_type, self._name)) + self.master = master else: raise ConfigError('not allowed value for group_type : {0}'.format( group_type)) def get_group_type(self): return self.group_type + + def get_master_name(self): + if self.master is None: + raise TypeError('get_master_name() shall not be called in case of' + 'non-master OptionDescription') + return self.master # ____________________________________________________________ "actions API" def hide(self): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index b8275f0..c2140a9 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -21,6 +21,12 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ +# available group_type values +_group_name = ('default', 'family', 'group') +groups_has_master = ('group', ) +class Group(str): pass +group_types = tuple(Group(i) for i in _group_name) + class Setting(): "``Config()``'s configuration options" # properties attribute: the name of a property enables this property