add more tests
[tiramisu.git] / tiramisu / autolib.py
1 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16 #
17 # The original `Config` design model is unproudly borrowed from
18 # the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
19 # the whole pypy projet is under MIT licence
20 # ____________________________________________________________
21 "enables us to carry out a calculation and return an option's value"
22 from tiramisu.error import PropertiesOptionError, ConfigError
23 from tiramisu.setting import multitypes
24 from tiramisu.i18n import _
25 # ____________________________________________________________
26
27
28 def carry_out_calculation(option, config, callback, callback_params,
29                           index=None, max_len=None):
30     """a function that carries out a calculation for an option's value
31
32     :param name: the option
33     :param config: the context config in order to have
34                    the whole options available
35     :param callback: the name of the callback function
36     :type callback: str
37     :param callback_params: the callback's parameters
38                             (only keyword parameters are allowed)
39     :type callback_params: dict
40     :param index: if an option is multi, only calculates the nth value
41     :type index: int
42     :param max_len: max length for a multi
43     :type max_len: int
44
45     The callback_params is a dict. Key is used to build args (if key is '')
46     and kwargs (otherwise). Values are tuple of:
47     - values
48     - tuple with option and boolean's force_permissive (True when don't raise
49     if PropertiesOptionError)
50     Values could have multiple values only when key is ''.
51
52     * if no callback_params:
53       => calculate(<function func at 0x2092320>, [], {})
54
55     * if callback_params={'': ('yes',)}
56       => calculate(<function func at 0x2092320>, ['yes'], {})
57
58     * if callback_params={'value': ('yes',)}
59       => calculate(<function func at 0x165b320>, [], {'value': 'yes'})
60
61     * if callback_params={'': ('yes', 'no')}
62       => calculate('yes', 'no')
63
64     * if callback_params={'value': ('yes', 'no')}
65       => ValueError()
66
67     * if callback_params={'': (['yes', 'no'],)}
68       => calculate(<function func at 0x176b320>, ['yes', 'no'], {})
69
70     * if callback_params={'value': ('yes', 'no')}
71       => raises ValueError()
72
73     * if callback_params={'': ((opt1, False),)}
74
75        - a simple option:
76          opt1 == 11
77          => calculate(<function func at 0x1cea320>, [11], {})
78
79        - a multi option and not master/slave:
80          opt1 == [1, 2, 3]
81          => calculate(<function func at 0x223c320>, [[1, 2, 3]], {})
82
83        - option is master or slave of opt1:
84          opt1 == [1, 2, 3]
85          => calculate(<function func at 0x223c320>, [1], {})
86          => calculate(<function func at 0x223c320>, [2], {})
87          => calculate(<function func at 0x223c320>, [3], {})
88
89       - opt is a master or slave but not related to option:
90         opt1 == [1, 2, 3]
91         => calculate(<function func at 0x11b0320>, [[1, 2, 3]], {})
92
93     * if callback_params={'value': ((opt1, False),)}
94
95        - a simple option:
96          opt1 == 11
97          => calculate(<function func at 0x17ff320>, [], {'value': 11})
98
99        - a multi option:
100          opt1 == [1, 2, 3]
101          => calculate(<function func at 0x1262320>, [], {'value': [1, 2, 3]})
102
103     * if callback_params={'': ((opt1, False), (opt2, False))}
104
105       - two single options
106           opt1 = 11
107           opt2 = 12
108           => calculate(<function func at 0x217a320>, [11, 12], {})
109
110       - a multi option with a simple option
111           opt1 == [1, 2, 3]
112           opt2 == 12
113           => calculate(<function func at 0x2153320>, [[1, 2, 3], 12], {})
114
115       - a multi option with an other multi option but with same length
116           opt1 == [1, 2, 3]
117           opt2 == [11, 12, 13]
118           => calculate(<function func at 0x1981320>, [[1, 2, 3], [11, 12, 13]], {})
119
120       - a multi option with an other multi option but with different length
121           opt1 == [1, 2, 3]
122           opt2 == [11, 12]
123           => calculate(<function func at 0x2384320>, [[1, 2, 3], [11, 12]], {})
124
125       - a multi option without value with a simple option
126           opt1 == []
127           opt2 == 11
128           => calculate(<function func at 0xb65320>, [[], 12], {})
129
130     * if callback_params={'value': ((opt1, False), (opt2, False))}
131       => raises ValueError()
132
133     If index is not None, return a value, otherwise return:
134
135     * a list if one parameters have multi option
136     * a value otherwise
137
138     If calculate return list, this list is extend to return value.
139     """
140     tcparams = {}
141     # if callback_params has a callback, launch several time calculate()
142     one_is_multi = False
143     # multi's option should have same value for all option
144     len_multi = None
145
146     for key, callbacks in callback_params.items():
147         for callbk in callbacks:
148             if isinstance(callbk, tuple):
149                 # callbk is something link (opt, True|False)
150                 opt, force_permissive = callbk
151                 path = config.cfgimpl_get_description().impl_get_path_by_opt(
152                     opt)
153                 # get value
154                 try:
155                     value = config._getattr(path, force_permissive=True)
156                 except PropertiesOptionError as err:
157                     if force_permissive:
158                         continue
159                     raise ConfigError(_('unable to carry out a calculation, '
160                                         'option {0} has properties: {1} for: '
161                                         '{2}').format(opt._name,
162                                                       err.proptype,
163                                                       option._name))
164
165                 is_multi = False
166                 if opt.impl_is_multi():
167                     #opt is master, search if option is a slave
168                     if opt.impl_get_multitype() == multitypes.master:
169                         if option in opt.impl_get_master_slaves():
170                             is_multi = True
171                     #opt is slave, search if option is an other slaves
172                     elif opt.impl_get_multitype() == multitypes.slave:
173                         if option in opt.impl_get_master_slaves().impl_get_master_slaves():
174                             is_multi = True
175                 if is_multi:
176                     len_multi = len(value)
177                     one_is_multi = True
178                 tcparams.setdefault(key, []).append((value, is_multi))
179             else:
180                 # callbk is a value and not a multi
181                 tcparams.setdefault(key, []).append((callbk, False))
182
183     # if one value is a multi, launch several time calculate
184     # if index is set, return a value
185     # if no index, return a list
186     if one_is_multi:
187         ret = []
188         if index:
189             range_ = [index]
190         else:
191             range_ = range(len_multi)
192         for incr in range_:
193             args = []
194             kwargs = {}
195             for key, couples in tcparams.items():
196                 for couple in couples:
197                     value, ismulti = couple
198                     if ismulti:
199                         val = value[incr]
200                     else:
201                         val = value
202                     if key == '':
203                         args.append(val)
204                     else:
205                         kwargs[key] = val
206             calc = calculate(callback, args, kwargs)
207             if index:
208                 ret = calc
209             else:
210                 ret.append(calc)
211         return ret
212     else:
213         # no value is multi
214         # return a single value
215         args = []
216         kwargs = {}
217         for key, couples in tcparams.items():
218             for couple in couples:
219                 # couple[1] (ismulti) is always False
220                 if key == '':
221                     args.append(couple[0])
222                 else:
223                     kwargs[key] = couple[0]
224         ret = calculate(callback, args, kwargs)
225         if callback_params != {}:
226             if isinstance(ret, list) and max_len:
227                 ret = ret[:max_len]
228                 if len(ret) < max_len:
229                     ret = ret + [None] * (max_len - len(ret))
230             if isinstance(ret, list) and index:
231                 if len(ret) < index + 1:
232                     ret = None
233                 else:
234                     ret = ret[index]
235         return ret
236
237
238 def calculate(callback, args, kwargs):
239     """wrapper that launches the 'callback'
240
241     :param callback: callback function
242     :param args: in the callback's arity, the unnamed parameters
243     :param kwargs: in the callback's arity, the named parameters
244
245     """
246     return callback(*args, **kwargs)