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