diff --git a/test/test_option_callback.py b/test/test_option_callback.py
index 843ae30..53b896f 100644
--- a/test/test_option_callback.py
+++ b/test/test_option_callback.py
@@ -8,7 +8,7 @@ from tiramisu.config import KernelConfig
from tiramisu.setting import groups, owners
from tiramisu import ChoiceOption, BoolOption, IntOption, FloatOption, \
StrOption, OptionDescription, SymLinkOption, IPOption, NetmaskOption, Leadership, \
- undefined, Params, ParamOption, ParamValue, ParamContext
+ undefined, Params, ParamOption, ParamValue, ParamContext, calc_value
from tiramisu.api import TIRAMISU_VERSION
from tiramisu.error import PropertiesOptionError, ConflictError, LeadershipError, ConfigError
from tiramisu.i18n import _
@@ -1194,3 +1194,100 @@ def test_callback_raise():
api.option('od2.opt2').value.get()
except ConfigError as err:
assert '"Option 2"' in str(err)
+
+
+def test_calc_value_simple():
+ val1 = StrOption('val1', '', 'val1')
+ val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1)))
+ od = OptionDescription('root', '', [val1, val2])
+ cfg = Config(od)
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1'}
+
+
+def test_calc_value_multi():
+ val1 = StrOption('val1', "", 'val1')
+ val2 = StrOption('val2', "", 'val2')
+ val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True)))
+ od = OptionDescription('root', '', [val1, val2, val3])
+ cfg = Config(od)
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']}
+
+
+def test_calc_value_disabled():
+ val1 = StrOption('val1', '', 'val1')
+ val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True), default=ParamValue('default_value')))
+ od = OptionDescription('root', '', [val1, val2])
+ cfg = Config(od)
+ cfg.property.read_write()
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1'}
+ cfg.option('val1').property.add('disabled')
+ assert cfg.value.dict() == {'val2': 'default_value'}
+
+
+def test_calc_value_condition():
+ boolean = BoolOption('boolean', '', True)
+ val1 = StrOption('val1', '', 'val1')
+ val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True),
+ default=ParamValue('default_value'),
+ condition=ParamOption(boolean),
+ expected=ParamValue(True)))
+ od = OptionDescription('root', '', [boolean, val1, val2])
+ cfg = Config(od)
+ cfg.property.read_write()
+ assert cfg.value.dict() == {'boolean': True, 'val1': 'val1', 'val2': 'val1'}
+ cfg.option('boolean').value.set(False)
+ assert cfg.value.dict() == {'boolean': False, 'val1': 'val1', 'val2': 'default_value'}
+
+
+def test_calc_value_allow_none():
+ from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ val1 = StrOption('val1', "", 'val1')
+ val2 = StrOption('val2', "")
+ val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), allow_none=ParamValue(True)))
+ od = OptionDescription('root', '', [val1, val2, val3])
+ cfg = Config(od)
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': None, 'val3': ['val1', None]}
+
+
+def test_calc_value_remove_duplicate():
+ from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ val1 = StrOption('val1', "", 'val1')
+ val2 = StrOption('val2', "", 'val1')
+ val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), remove_duplicate_value=ParamValue(True)))
+ od = OptionDescription('root', '', [val1, val2, val3])
+ cfg = Config(od)
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
+
+
+def test_calc_value_join():
+ from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ val1 = StrOption('val1', "", 'val1')
+ val2 = StrOption('val2', "", 'val2')
+ val3 = StrOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), join=ParamValue('.')))
+ od = OptionDescription('root', '', [val1, val2, val3])
+ cfg = Config(od)
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': 'val1.val2'}
+
+
+def test_calc_value_min():
+ from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ val1 = StrOption('val1', "", 'val1')
+ val2 = StrOption('val2', "", 'val2')
+ val3 = StrOption('val3', "", 'val3')
+ val4 = StrOption('val4', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2), ParamOption(val3, True)), join=ParamValue('.'), min_args_len=ParamValue(3)))
+ od = OptionDescription('root', '', [val1, val2, val3, val4])
+ cfg = Config(od)
+ cfg.property.read_write()
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
+ cfg.option('val3').property.add('disabled')
+ assert cfg.value.dict() == {'val1': 'val1', 'val2': 'val2', 'val4': ''}
+
+
+def test_calc_value_add():
+ from tiramisu import calc_value, IntOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ val1 = IntOption('val1', "", 1)
+ val2 = IntOption('val2', "", 2)
+ val3 = IntOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), operator=ParamValue('add')))
+ od = OptionDescription('root', '', [val1, val2, val3])
+ cfg = Config(od)
+ assert cfg.value.dict() == {'val1': 1, 'val2': 2, 'val3': 3}
diff --git a/tiramisu/__init__.py b/tiramisu/__init__.py
index 4826832..db7fad8 100644
--- a/tiramisu/__init__.py
+++ b/tiramisu/__init__.py
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
from .function import Params, ParamOption, ParamValue, ParamContext, \
- tiramisu_copy
+ tiramisu_copy, calc_value
from .option import *
from .error import APIError
from .api import Config, MetaConfig, GroupConfig, MixConfig
@@ -37,7 +37,8 @@ allfuncs = ['Params',
'Storage',
'list_sessions',
'delete_session',
- 'tiramisu_copy']
+ 'tiramisu_copy',
+ 'calc_value']
allfuncs.extend(all_options)
del(all_options)
__all__ = tuple(allfuncs)
diff --git a/tiramisu/function.py b/tiramisu/function.py
index 46bf1c0..94837a1 100644
--- a/tiramisu/function.py
+++ b/tiramisu/function.py
@@ -12,6 +12,9 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
+from typing import Any, List, Optional
+from operator import add, mul, sub, truediv
+from .setting import undefined
from .i18n import _
@@ -79,3 +82,235 @@ class ParamIndex(Param):
def tiramisu_copy(val): # pragma: no cover
return val
+
+
+def calc_value(*args: List[Any],
+ multi: bool=False,
+ default: Any=undefined,
+ condition: Any=undefined,
+ expected: Any=undefined,
+ condition_operator: str='AND',
+ allow_none: bool=False,
+ remove_duplicate_value: bool=False,
+ join: Optional[str]=None,
+ min_args_len: Optional[int]=None,
+ operator: Optional[str]=None,
+ **kwargs) -> Any:
+ """calculate value
+ :param multi: value returns must be a list of value
+ :param default: default value if condition is not matched or if args is empty
+ if there is more than one default value, set default_0, default_1, ...
+ :param condition: test if condition is equal to expected value
+ if there is more than one condition, set condition_0, condition_1, ...
+ :param expected: value expected for all conditions
+ if expected value is different between condition, set expected_0, expected_1, ...
+ :param condition_operator: OR or AND operator for condition
+ :param allow_none: if False, do not return list in None is present in list
+ :param remove_duplicate_value: if True, remote duplicated value
+ :param join: join all args with specified characters
+ :param min_args_len: if number of arguments is smaller than this value, return default value
+ :param operator: operator
+
+ examples:
+ * you want to copy value from an option to an other option:
+ >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption
+ >>> val1 = StrOption('val1', '', 'val1')
+ >>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1)))
+ >>> od = OptionDescription('root', '', [val1, val2])
+ >>> cfg = Config(od)
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': 'val1'}
+
+ * you want to copy values from two options in one multi option
+ >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> val1 = StrOption('val1', "", 'val1')
+ >>> val2 = StrOption('val2', "", 'val2')
+ >>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True)))
+ >>> od = OptionDescription('root', '', [val1, val2, val3])
+ >>> cfg = Config(od)
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': 'val2', 'val3': ['val1', 'val2']}
+
+ * you want to copy a value from an option is it not disabled, otherwise set 'default_value'
+ >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> val1 = StrOption('val1', '', 'val1')
+ >>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True), default=ParamValue('default_value')))
+ >>> od = OptionDescription('root', '', [val1, val2])
+ >>> cfg = Config(od)
+ >>> cfg.property.read_write()
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': 'val1'}
+ >>> cfg.option('val1').property.add('disabled')
+ >>> cfg.value.dict()
+ {'val2': 'default_value'}
+
+ * you want to copy value from an option is an other is True, otherwise set 'default_value'
+ >>> from tiramisu import calc_value, BoolOption, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> boolean = BoolOption('boolean', '', True)
+ >>> val1 = StrOption('val1', '', 'val1')
+ >>> val2 = StrOption('val2', '', callback=calc_value, callback_params=Params(ParamOption(val1, True),
+ ... default=ParamValue('default_value'),
+ ... condition=ParamOption(boolean),
+ ... expected=ParamValue(True)))
+ >>> od = OptionDescription('root', '', [boolean, val1, val2])
+ >>> cfg = Config(od)
+ >>> cfg.property.read_write()
+ >>> cfg.value.dict()
+ {'boolean': True, 'val1': 'val1', 'val2': 'val1'}
+ >>> cfg.option('boolean').value.set(False)
+ >>> cfg.value.dict()
+ {'boolean': False, 'val1': 'val1', 'val2': 'default_value'}
+
+ * you want to copy option even if None is present
+ >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> val1 = StrOption('val1', "", 'val1')
+ >>> val2 = StrOption('val2', "")
+ >>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), allow_none=ParamValue(True)))
+ >>> od = OptionDescription('root', '', [val1, val2, val3])
+ >>> cfg = Config(od)
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': None, 'val3': ['val1', None]}
+
+ * you want uniq value
+ >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> val1 = StrOption('val1', "", 'val1')
+ >>> val2 = StrOption('val2', "", 'val1')
+ >>> val3 = StrOption('val3', "", multi=True, callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), multi=ParamValue(True), remove_duplicate_value=ParamValue(True)))
+ >>> od = OptionDescription('root', '', [val1, val2, val3])
+ >>> cfg = Config(od)
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': 'val1', 'val3': ['val1']}
+
+
+ * you want to join two values with '.'
+ >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> val1 = StrOption('val1', "", 'val1')
+ >>> val2 = StrOption('val2', "", 'val2')
+ >>> val3 = StrOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), join=ParamValue('.')))
+ >>> od = OptionDescription('root', '', [val1, val2, val3])
+ >>> cfg = Config(od)
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': 'val2', 'val3': 'val1.val2'}
+
+ * you want join three values, only if almost three values are set
+ >>> from tiramisu import calc_value, StrOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> val1 = StrOption('val1', "", 'val1')
+ >>> val2 = StrOption('val2', "", 'val2')
+ >>> val3 = StrOption('val3', "", 'val3')
+ >>> val4 = StrOption('val4', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2), ParamOption(val3, True)), join=ParamValue('.'), min_args_len=ParamValue(3)))
+ >>> od = OptionDescription('root', '', [val1, val2, val3, val4])
+ >>> cfg = Config(od)
+ >>> cfg.property.read_write()
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': 'val2', 'val3': 'val3', 'val4': 'val1.val2.val3'}
+ >>> cfg.option('val3').property.add('disabled')
+ >>> cfg.value.dict()
+ {'val1': 'val1', 'val2': 'val2', 'val4': ''}
+
+ * you want to add all values
+ >>> from tiramisu import calc_value, IntOption, OptionDescription, Config, Params, ParamOption, ParamValue
+ >>> val1 = IntOption('val1', "", 1)
+ >>> val2 = IntOption('val2', "", 2)
+ >>> val3 = IntOption('val3', "", callback=calc_value, callback_params=Params((ParamOption(val1), ParamOption(val2)), operator=ParamValue('add')))
+ >>> od = OptionDescription('root', '', [val1, val2, val3])
+ >>> cfg = Config(od)
+ >>> cfg.value.dict()
+ {'val1': 1, 'val2': 2, 'val3': 3}
+
+ """
+ def value_from_kwargs(value: Any, pattern: str, to_dict: bool=False) -> Any:
+ # if value attribute exist return it's value
+ # otherwise pattern_0, pattern_1, ...
+ # otherwise undefined
+ if value is not undefined:
+ if to_dict == 'all':
+ returns = {0: value}
+ else:
+ returns = value
+ else:
+ kwargs_matches = {}
+ len_pattern = len(pattern)
+ for key in kwargs.keys():
+ if key.startswith(pattern):
+ index = int(key[len_pattern:])
+ kwargs_matches[index] = kwargs[key]
+ if not kwargs_matches:
+ return undefined
+ keys = sorted(kwargs_matches)
+ if to_dict:
+ returns = {}
+ else:
+ returns = []
+ for key in keys:
+ if to_dict:
+ returns[key] = kwargs_matches[key]
+ else:
+ returns.append(kwargs_matches[key])
+ return returns
+
+ def is_condition_matches():
+ calculated_conditions = value_from_kwargs(condition, 'condition_', to_dict='all')
+ if condition is not undefined:
+ is_matches = None
+ calculated_expected = value_from_kwargs(expected, 'expected_', to_dict=True)
+ for idx, calculated_condition in calculated_conditions.items():
+ if isinstance(calculated_expected, dict):
+ current_matches = calculated_condition == calculated_expected[idx]
+ else:
+ current_matches = calculated_condition == calculated_expected
+ if is_matches is None:
+ is_matches = current_matches
+ elif condition_operator == 'AND':
+ is_matches = is_matches and current_matches
+ elif condition_operator == 'OR':
+ is_matches = is_matches or current_matches
+ else:
+ raise ValueError(_('unexpected {} condition_operator in calc_value').format(condition_operator))
+ else:
+ is_matches = True
+ return is_matches
+
+ def get_value():
+ if not is_condition_matches():
+ # force to default
+ value = []
+ else:
+ value = list(args)
+ if min_args_len and not len(value) >= min_args_len:
+ value = []
+ if value == []:
+ # default value
+ new_default = value_from_kwargs(default, 'default_')
+ if new_default is not undefined:
+ if not isinstance(new_default, list):
+ value = [new_default]
+ else:
+ value = new_default
+ return value
+
+ value = get_value()
+ if not multi:
+ if join is not None:
+ value = join.join(value)
+ elif value and operator:
+ new_value = value[0]
+ op = {'mul': mul,
+ 'add': add,
+ 'div': truediv,
+ 'sub': sub}[operator]
+ for val in value[1:]:
+ new_value = op(new_value, val)
+ value = new_value
+ elif value == []:
+ value = None
+ else:
+ value = value[0]
+ elif None in value and not allow_none:
+ value = []
+ elif remove_duplicate_value:
+ new_value = []
+ for val in value:
+ if val not in new_value:
+ new_value.append(val)
+ value = new_value
+ return value