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