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