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