generate correct len for slave if no value
[tiramisu.git] / tiramisu / value.py
1 # -*- coding: utf-8 -*-
2 "takes care of the option's values and multi values"
3 # Copyright (C) 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 from tiramisu.error import NoValueReturned, MandatoryError, MultiTypeError
24 from tiramisu.setting import owners, multitypes
25
26 class Values(object):
27     def __init__(self, context):
28         """
29         Initializes the values's dict.
30
31         :param context: the context is the home config's values and properties
32         """
33         self.owners = {}
34         "Config's root indeed is in charge of the `Option()`'s values"
35         self.values = {}
36         self.previous_values = {}
37         self.masters = {}
38         self.slaves = {}
39         self.context = context
40
41     def _get_multitype(self, opt):
42         if opt in self.slaves:
43             # slave
44             multitype = multitypes.slave
45         elif opt in self.masters:
46             # master
47             multitype = multitypes.master
48         # FIXME : default value for a multi, we shall work on groups
49         else:
50             multitype = multitypes.default
51         return multitype
52
53     def _get_value(self, opt):
54         "special case for the multis: they never return None"
55         if opt not in self.values:
56             if opt.is_multi():
57                 multitype = self._get_multitype(opt)
58                 value = Multi(opt.getdefault(), self.context, opt, multitype)
59             else:
60                 value = opt.getdefault()
61
62             if opt in self.slaves:
63                 masterpath = self.context._cfgimpl_all_paths[self.slaves[opt]]
64                 mastervalue = getattr(self.context, masterpath)
65                 masterlen = len(mastervalue)
66                 if len(value) < masterlen:
67                     for num in range(0, masterlen - len(value)):
68                         value.append(None, force=True)
69             return value
70         return self.values[opt]
71
72     def reset(self, opt):
73         if opt in self.values:
74             self.set_previous_value(opt)
75             del(self.values[opt])
76         self.setowner(opt, owners.default)
77
78     def set_previous_value(self, opt):
79         if opt in self.values:
80             old_value = self.values[opt]
81         elif opt.is_multi():
82             old_value = []
83         else:
84             old_value = None
85         if type(old_value) == Multi:
86            self.previous_values[opt] = list(old_value)
87         else:
88            self.previous_values[opt] = old_value
89
90     def get_previous_value(self, opt):
91         if opt in self.previous_values:
92             prec_value = self.previous_values[opt]
93         elif opt.is_multi():
94             prec_value = []
95         else:
96             prec_value = None
97         return prec_value
98
99     def _is_empty(self, opt, value=None):
100         "convenience method to know if an option is empty"
101         if value is not None:
102             return False
103         if (not opt.is_multi() and value == None) or \
104             (opt.is_multi() and (value == [] or \
105                 None in self._get_value(opt))):
106             return True
107         return False
108
109     def is_empty(self, opt):
110         if opt not in self.values:
111             return True
112         value = self.values[opt]
113         if not opt.is_multi():
114             if self._get_value(opt) == None:
115                 return True
116             return False
117         else:
118             value = list(value)
119             for val in value:
120                 if val != None:
121                     return False
122             return True
123
124     def _test_mandatory(self, opt, value=None):
125         # mandatory options
126         mandatory = self.context._cfgimpl_settings.mandatory
127         if opt.is_mandatory() and mandatory:
128             if self._is_empty(opt, value) and \
129                     opt.is_empty_by_default():
130                 raise MandatoryError("option: {0} is mandatory "
131                                       "and shall have a value".format(opt._name))
132
133     def fill_multi(self, opt, result):
134         """fills a multi option with default and calculated values
135         """
136         value = self._get_value(opt)
137         if not isinstance(result, list):
138             _result = [result]
139         else:
140             _result = result
141         multitype = self._get_multitype(opt)
142         return Multi(_result, self.context, opt, multitype)
143
144     def __getitem__(self, opt):
145         # options with callbacks
146         value = self._get_value(opt)
147         if opt.has_callback():
148             if (not opt.is_frozen() or \
149                     not opt.is_forced_on_freeze()) and \
150                     not opt.is_default_owner(self.context):
151                 return self._get_value(opt)
152             try:
153                 result = opt.getcallback_value(
154                         self.context)
155             except NoValueReturned, err:
156                 pass
157             else:
158                 if opt.is_multi():
159                     value = self.fill_multi(opt, result)
160                 else:
161                     # this result **shall not** be a list
162                     if isinstance(result, list):
163                         raise ConfigError('invalid calculated value returned '
164                             'for option {0} : shall not be a list'.format(name))
165                     value = result
166                 if value != None and not opt.validate(value,
167                             self.context._cfgimpl_settings.validator):
168                     raise ConfigError('invalid calculated value returned'
169                         ' for option {0}'.format(name))
170         # frozen and force default
171         if not opt.has_callback() and opt.is_forced_on_freeze():
172             value = opt.getdefault()
173             if opt.is_multi():
174                 value = self.fill_multi(opt, value)
175         self._test_mandatory(opt, value)
176         return value
177
178     def __setitem__(self, opt, value):
179         if opt in self.masters:
180             masterlen = len(value)
181             for slave in self.masters[opt]:
182                 value_slave = self._get_value(slave)
183                 if len(value_slave) > masterlen:
184                     raise MultiTypeError("invalid len for the slave: {0}"
185                     " which has {1} as master".format(slave._name,
186                                                       opt._name))
187                 elif len(value_slave) < masterlen:
188                     for num in range(0, masterlen - len(value_slave)):
189                         value_slave.append(None, force=True)
190
191         elif opt in self.slaves:
192             if len(self._get_value(self.slaves[opt])) != len(value):
193                    raise MultiTypeError("invalid len for the slave: {0}"
194                     " which has {1} as master".format(opt._name,
195                                                       self.slaves[opt]._name))
196         if opt.is_multi() and not isinstance(value, Multi):
197                 value = Multi(value, self.context, opt, multitypes.default)
198         self.setitem(opt, value)
199
200     def setitem(self, opt, value):
201         self.set_previous_value(opt)
202         if type(value) == list:
203             raise MultiTypeError("the type of the value {0} which is multi shall "
204                                  "be Multi and not list".format(str(value)))
205         self.values[opt] = value
206         self.setowner(opt, self.context._cfgimpl_settings.getowner())
207
208     def __contains__(self, opt):
209         return opt in self.values
210     #____________________________________________________________
211     def setowner(self, opt, owner):
212         if isinstance(owner, owners.Owner):
213             self.owners[opt] = owner
214         else:
215             raise OptionValueError("Bad owner: " + str(owner))
216
217     def getowner(self, opt):
218         return self.owners.get(opt, owners.default)
219
220 # ____________________________________________________________
221 # multi types
222 class Multi(list):
223     """multi options values container
224     that support item notation for the values of multi options"""
225     def __init__(self, lst, context, opt, multitype):
226         """
227         :param lst: the Multi wraps a list value
228         :param context: the home config that has the settings and the values
229         :param opt: the option object that have this Multi value
230         """
231         self.settings = context._cfgimpl_settings
232         self.opt = opt
233         self.values = context._cfgimpl_values
234         self.multitype = multitype
235         super(Multi, self).__init__(lst)
236         if multitype == multitypes.master:
237             self.slaves = context._cfgimpl_values.masters[opt]
238         else:
239             self.slaves = None
240     def __setitem__(self, key, value):
241         self._validate(value)
242         self.values[self.opt] = self
243         super(Multi, self).__setitem__(key, value)
244
245     def append(self, value, force=False):
246         """the list value can be updated (appened)
247         only if the option is a master
248         """
249         if not force:
250             if self.multitype == multitypes.slave:
251                 raise MultiTypeError("cannot append a value on a multi option {0}"
252                         " which is a slave".format(self.opt._name))
253             elif self.multitype == multitypes.master:
254                 for slave in self.slaves:
255                     self.values[slave].append(None, force=True)
256         self._validate(value)
257         self.values.setitem(self.opt, self)
258         super(Multi, self).append(value)
259
260     def _validate(self, value):
261         if value != None and not self.opt._validate(value):
262             raise ConfigError("invalid value {0} "
263                     "for option {1}".format(str(value), self.opt._name))
264
265     def pop(self, key, force=False):
266         """the list value can be updated (poped)
267         only if the option is a master
268
269         :param key: index of the element to pop
270         :return: the requested element
271         """
272         if not force:
273             if self.multitype == multitypes.slave:
274                 raise MultiTypeError("cannot append a value on a multi option {0}"
275                         " which is a slave".format(self.opt._name))
276             elif self.multitype == multitypes.master:
277                 for slave in self.slaves:
278                     self.values[slave].pop(key, force=True)
279         self.values.setitem(self.opt, self)
280         return super(Multi, self).pop(key)