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