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