ddad280a94639af3bbab4a3a3408475de9c83b0e
[tiramisu.git] / tiramisu / storage / dictionary / option.py
1 # -*- coding: utf-8 -*-
2 ""
3 # Copyright (C) 2014 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.i18n import _
21 from tiramisu.setting import undefined
22 from tiramisu.error import ConfigError
23
24
25 #____________________________________________________________
26 #
27 # Base
28 #('_name', '_informations', '_multi', '_multitype', '_warnings_only', '_extra', '_readonly', '_subdyn)
29 class StorageBase(object):
30     __slots__ = ('_name',
31                  '_informations',
32                  '_multi',
33                  '_extra',
34                  '_warnings_only',
35                  #valeur
36                  '_default',
37                  '_default_multi',
38                  #calcul
39                  '_subdyn',
40                  '_requires',
41                  '_properties',
42                  '_calc_properties',
43                  '_val_call',
44                  #'_validator',
45                  #'_validator_params',
46                  #'_callback',
47                  #'_callback_params',
48                  '_consistencies',
49                  '_master_slaves',
50                  '_choice_values',
51                  '_choice_values_params',
52                  #autre
53                  '_state_master_slaves',
54                  '_state_callback',
55                  '_state_callback_params',
56                  '_state_requires',
57                  '_stated',
58                  '_state_consistencies',
59                  '_state_informations',
60                  '_state_readonly',
61                  '__weakref__'
62                  )
63
64     def __init__(self, name, multi, warnings_only, doc, extra, calc_properties,
65                  requires, properties, opt=undefined):
66         self._name = name
67         if doc is not undefined:
68             self._informations = {'doc': doc}
69             if multi != 1:
70                 self._multi = multi
71             if extra is not None:
72                 self._extra = extra
73         if warnings_only is True:
74             self._warnings_only = warnings_only
75
76         if calc_properties is not undefined:
77             self._calc_properties = calc_properties
78         if requires is not undefined:
79             self._requires = requires
80         if properties is not undefined:
81             self._properties = properties
82         if opt is not undefined:
83             self._opt = opt
84
85     def _set_default_values(self, default, default_multi):
86         if self.impl_is_multi() and default is None:
87                 default = []
88         self.impl_validate(default)
89         if ((self.impl_is_multi() and default != tuple()) or
90                 (not self.impl_is_multi() and default is not None)):
91             if self.impl_is_multi():
92                 default = tuple(default)
93             self._default = default
94
95         if self.impl_is_multi() and default_multi is not None:
96             try:
97                 self._validate(default_multi)
98             except ValueError as err:  # pragma: optional cover
99                 raise ValueError(_("invalid default_multi value {0} "
100                                    "for option {1}: {2}").format(
101                                        str(default_multi),
102                                        self.impl_getname(), err))
103             self._default_multi = default_multi
104
105     # information
106     def impl_set_information(self, key, value):
107         """updates the information's attribute
108         (which is a dictionary)
109
110         :param key: information's key (ex: "help", "doc"
111         :param value: information's value (ex: "the help string")
112         """
113         self._informations[key] = value
114
115     def impl_get_information(self, key, default=undefined):
116         """retrieves one information's item
117
118         :param key: the item string (ex: "help")
119         """
120         error = False
121         dico = self._informations
122         if dico is None or isinstance(dico, str) or isinstance(dico, unicode):
123             if key == 'doc':
124                 return dico
125             error = True
126         elif isinstance(dico, tuple):
127             try:
128                 return dico[1][dico[0].index(key)]
129             except AttributeError:
130                 if default is not undefined:
131                     return default
132                 error = True
133         else:
134             if default is not undefined:
135                 return self._informations.get(key, default)
136             try:
137                 return self._informations[key]
138             except KeyError:  # pragma: optional cover
139                 error = True
140         if error:
141             raise ValueError(_("information's item not found: {0}").format(
142                 key))
143
144     def _add_consistency(self, func, all_cons_opts, params):
145         cons = (func, all_cons_opts, params)
146         try:
147             self._consistencies.append(cons)
148         except AttributeError:
149             self._consistencies = [cons]
150
151     def _get_consistencies(self):
152         try:
153             return self._consistencies
154         except AttributeError:
155             return tuple()
156
157     def _set_callback(self, callback, callback_params):
158         if callback_params is None or callback_params == {}:
159             val_call = (callback,)
160         else:
161             val_call = tuple([callback, callback_params])
162         try:
163             self._val_call = (self._val_call[0], val_call)
164         except AttributeError:
165             self._val_call = (None, val_call)
166
167     def impl_get_callback(self):
168         default = (None, {})
169         try:
170             call = self._val_call[1]
171         except (AttributeError, IndexError):
172             ret_call = default
173         else:
174             if call is None:
175                 ret_call = default
176             else:
177                 if len(call) == 1:
178                     ret_call = (call[0], default[1])
179                 else:
180                     ret_call = call
181         return ret_call
182
183     def impl_get_calc_properties(self):
184         try:
185             return self._calc_properties
186         except AttributeError:
187             return frozenset()
188
189     def impl_getrequires(self):
190         try:
191             return self._requires
192         except AttributeError:
193             return []
194
195     def _set_validator(self, validator, validator_params):
196         if validator_params is None:
197             val_call = (validator,)
198         else:
199             val_call = (validator, validator_params)
200         try:
201             self._val_call = (val_call, self._val_call[1])
202         except (AttributeError, IndexError):
203             self._val_call = (val_call, None)
204
205     def impl_get_validator(self):
206         default = (None, {})
207         try:
208             val = self._val_call[0]
209         except (AttributeError, IndexError):
210             ret_val = default
211         else:
212             if val is None:
213                 ret_val = default
214             else:
215                 if len(val) == 1:
216                     ret_val = (val[0], default[1])
217                 else:
218                     ret_val = val
219         return ret_val
220
221     def _get_id(self):
222         return id(self)
223
224     def _impl_getsubdyn(self):
225         return self._subdyn
226
227     def _impl_getopt(self):
228         return self._opt
229
230     def _set_readonly(self):
231         if not self.impl_is_readonly():
232             dico = self._informations
233             if not (dico is None or isinstance(dico, str) or isinstance(dico, unicode)):
234                 keys = tuple(dico.keys())
235                 if keys == ('doc',):
236                     dico = dico['doc']
237                 else:
238                     dico = tuple([tuple(dico.keys()), tuple(dico.values())])
239             self._informations = dico
240             try:
241                 extra = self._extra
242                 self._extra = tuple([tuple(extra.keys()), tuple(extra.values())])
243             except AttributeError:
244                 pass
245
246     def _impl_setsubdyn(self, subdyn):
247         self._subdyn = subdyn
248
249     def _impl_setopt(self, opt):
250         self._opt = opt
251
252     def _impl_convert_informations(self, descr, load=False):
253         if not load:
254             infos = self._informations
255             if isinstance(infos, tuple):
256                 self._state_informations = {}
257                 for key, value in infos:
258                     self._state_informations[key] = value
259             elif isinstance(infos, str) or isinstance(infos, unicode):
260                 self._state_informations = {'doc': infos}
261             else:
262                 self._state_informations = infos
263             self._state_readonly = self.impl_is_readonly()
264         else:
265             try:
266                 self._informations = self._state_informations
267                 del(self._state_informations)
268             except AttributeError:
269                 pass
270             if self._state_readonly:
271                 self._set_readonly()
272             del(self._state_readonly)
273
274     def _impl_getattributes(self):
275         slots = set()
276         for subclass in self.__class__.__mro__:
277             if subclass is not object:
278                 slots.update(subclass.__slots__)
279         return slots
280
281     def impl_is_readonly(self):
282         try:
283             return not isinstance(self._informations, dict)
284         except AttributeError:
285             return False
286
287     def impl_getname(self):
288         return self._name
289
290     def impl_is_multi(self):
291         try:
292             _multi = self._multi
293         except AttributeError:
294             _multi = 1
295         return _multi == 0 or _multi == 2
296
297     def impl_is_submulti(self):
298         try:
299             _multi = self._multi
300         except IndexError:
301             _multi = 1
302         return _multi == 2
303
304     def _get_extra(self, key):
305         extra = self._extra
306         if isinstance(extra, tuple):
307             return extra[1][extra[0].index(key)]
308         else:
309             return extra[key]
310
311     def _is_warnings_only(self):
312         try:
313             return self._warnings_only
314         except:
315             return False
316
317     def impl_getdefault(self):
318         "accessing the default value"
319         try:
320             ret = self._default
321             if self.impl_is_multi():
322                 return list(ret)
323             return ret
324         except AttributeError:
325             if self.impl_is_multi():
326                 return []
327             return None
328
329     def impl_getdefault_multi(self):
330         "accessing the default value for a multi"
331         if self.impl_is_multi():
332             try:
333                 return self._default_multi
334             except (AttributeError, IndexError):
335                 pass
336
337     def commit(self):
338         pass
339
340
341 class StorageOptionDescription(StorageBase):
342     __slots__ = ('_children', '_cache_paths', '_cache_consistencies',
343                  '_group_type', '_is_build_cache', '_state_group_type')
344
345     def __init__(self, name, multi, warnings_only, doc, extra):
346         super(StorageOptionDescription, self).__init__(name, multi,
347                                                        warnings_only, doc,
348                                                        None, undefined,
349                                                        undefined, undefined)
350         self._cache_paths = None
351
352     def _add_children(self, child_names, children):
353         self._children = (tuple(child_names), tuple(children))
354
355     def impl_already_build_caches(self):
356         return self._is_build_cache
357
358     def impl_get_opt_by_path(self, path):
359         try:
360             return self._cache_paths[0][self._cache_paths[1].index(path)]
361         except ValueError:  # pragma: optional cover
362             raise AttributeError(_('no option for path {0}').format(path))
363
364     def impl_get_path_by_opt(self, opt):
365         try:
366             return self._cache_paths[1][self._cache_paths[0].index(opt)]
367         except ValueError:  # pragma: optional cover
368             raise AttributeError(_('no option {0} found').format(opt))
369
370     def impl_get_group_type(self):  # pragma: optional cover
371         return self._group_type
372
373     def impl_build_cache_option(self, _currpath=None, cache_path=None,
374                                 cache_option=None):
375         try:
376             self._cache_paths
377         except AttributeError:
378             self._cache_paths = None
379         if _currpath is None and self._cache_paths is not None:  # pragma: optional cover
380             # cache already set
381             return
382         if _currpath is None:
383             save = True
384             _currpath = []
385         else:
386             save = False
387         if cache_path is None:
388             cache_path = []
389             cache_option = []
390         for option in self._impl_getchildren(dyn=False):
391             attr = option.impl_getname()
392             path = str('.'.join(_currpath + [attr]))
393             cache_option.append(option)
394             cache_path.append(path)
395             if option.impl_is_optiondescription():
396                 _currpath.append(attr)
397                 option.impl_build_cache_option(_currpath, cache_path,
398                                                cache_option)
399                 _currpath.pop()
400         if save:
401             self._cache_paths = (tuple(cache_option), tuple(cache_path))
402
403     def impl_get_options_paths(self, bytype, byname, _subpath, only_first, context):
404         find_results = []
405
406         def _rebuild_dynpath(path, suffix, dynopt):
407             found = False
408             spath = path.split('.')
409             for length in xrange(1, len(spath)):
410                 subpath = '.'.join(spath[0:length])
411                 subopt = self.impl_get_opt_by_path(subpath)
412                 if dynopt == subopt:
413                     found = True
414                     break
415             if not found:
416                 raise ConfigError(_('cannot find dynpath'))
417             subpath = subpath + suffix
418             for slength in xrange(length, len(spath)):
419                 subpath = subpath + '.' + spath[slength] + suffix
420             return subpath
421
422         def _filter_by_name(path, option):
423             name = option.impl_getname()
424             if option._is_subdyn():
425                 if byname.startswith(name):
426                     found = False
427                     for suffix in option._subdyn._impl_get_suffixes(
428                             context):
429                         if byname == name + suffix:
430                             found = True
431                             path = _rebuild_dynpath(path, suffix,
432                                                     option._subdyn)
433                             option = option._impl_to_dyn(
434                                 name + suffix, path)
435                             break
436                     if not found:
437                         return False
438             else:
439                 if not byname == name:
440                     return False
441             find_results.append((path, option))
442             return True
443
444         def _filter_by_type(path, option):
445             if isinstance(option, bytype):
446                 #if byname is not None, check option byname in _filter_by_name
447                 #not here
448                 if byname is None:
449                     if option._is_subdyn():
450                         name = option.impl_getname()
451                         for suffix in option._subdyn._impl_get_suffixes(
452                                 context):
453                             spath = _rebuild_dynpath(path, suffix,
454                                                      option._subdyn)
455                             find_results.append((spath, option._impl_to_dyn(
456                                 name + suffix, spath)))
457                     else:
458                         find_results.append((path, option))
459                 return True
460             return False
461
462         def _filter(path, option):
463             if bytype is not None:
464                 retval = _filter_by_type(path, option)
465                 if byname is None:
466                     return retval
467             if byname is not None:
468                 return _filter_by_name(path, option)
469
470         opts, paths = self._cache_paths
471         for index in range(0, len(paths)):
472             option = opts[index]
473             if option.impl_is_optiondescription():
474                 continue
475             path = paths[index]
476             if _subpath is not None and not path.startswith(_subpath + '.'):
477                 continue
478             if bytype == byname is None:
479                 if option._is_subdyn():
480                     name = option.impl_getname()
481                     for suffix in option._subdyn._impl_get_suffixes(
482                             context):
483                         spath = _rebuild_dynpath(path, suffix,
484                                                  option._subdyn)
485                         find_results.append((spath, option._impl_to_dyn(
486                             name + suffix, spath)))
487                 else:
488                     find_results.append((path, option))
489             else:
490                 if _filter(path, option) is False:
491                     continue
492             if only_first:
493                 return find_results[0]
494         return find_results
495
496     def _impl_st_getchildren(self, context, only_dyn=False):
497         for child in self._children[1]:
498             if only_dyn is False or child.impl_is_dynoptiondescription():
499                 yield(child)
500
501     def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
502         error = False
503         if suffix is not undefined:
504             try:
505                 if undefined in [suffix, context]:  # pragma: optional cover
506                     raise ConfigError(_("suffix and context needed if "
507                                         "it's a dyn option"))
508                 if name.endswith(suffix):
509                     oname = name[:-len(suffix)]
510                     child = self._children[1][self._children[0].index(oname)]
511                     return self._impl_get_dynchild(child, suffix)
512                 else:
513                     error = True
514             except ValueError:  # pragma: optional cover
515                 error = True
516         else:
517             try:  # pragma: optional cover
518                 if name == '_readonly':
519                     raise AttributeError(_("{0} instance has no attribute "
520                                          "'_readonly'").format(
521                                              self.__class__.__name__))
522                 child = self._children[1][self._children[0].index(name)]
523                 if dyn and child.impl_is_dynoptiondescription():
524                     error = True
525                 else:
526                     return child
527             except ValueError:
528                 child = self._impl_search_dynchild(name, context=context)
529                 if child != []:
530                     return child
531                 error = True
532         if error:
533             raise AttributeError(_('unknown Option {0} '
534                                    'in OptionDescription {1}'
535                                    '').format(name, self.impl_getname()))
536
537     def _get_force_store_value(self):
538         #FIXME faire des tests (notamment pas ajouter à un config)
539         #FIXME devrait faire un cache !
540         opts, paths = self._cache_paths
541         for index in range(0, len(paths)):
542             opt = opts[index]
543             path = paths[index]
544             if 'force_store_value' in opt._properties:
545                 yield (opt, path)