relative imports to absolute imports (fixes #2667)
authorgwen <gremond@cadoles.com>
Mon, 23 Jul 2012 12:39:16 +0000 (14:39 +0200)
committergwen <gremond@cadoles.com>
Mon, 23 Jul 2012 12:39:16 +0000 (14:39 +0200)
15 files changed:
setup.py
src/__init__.py [deleted file]
src/autolib.py [deleted file]
src/basetype.py [deleted file]
src/config.py [deleted file]
src/error.py [deleted file]
src/option.py [deleted file]
src/tool.py [deleted file]
tiramisu/__init__.py [new file with mode: 0644]
tiramisu/autolib.py [new file with mode: 0644]
tiramisu/basetype.py [new file with mode: 0644]
tiramisu/config.py [new file with mode: 0644]
tiramisu/error.py [new file with mode: 0644]
tiramisu/option.py [new file with mode: 0644]
tiramisu/tool.py [new file with mode: 0644]

index b523bb8..b425011 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -9,6 +9,6 @@ setup(
     version='1.0',
     description='configuration management tool',
     url='http://labs.libre-entreprise.org/projects/tiramisu',
-    package_dir = {'tiramisu':'src'},
+    package_dir = {'tiramisu':'tiramisu'},
     packages=['tiramisu']
 )
diff --git a/src/__init__.py b/src/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/src/autolib.py b/src/autolib.py
deleted file mode 100644 (file)
index 988a6b2..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright (C) 2012 Team tiramisu (see README for all contributors)
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-# The original `Config` design model is unproudly borrowed from 
-# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
-# the whole pypy projet is under MIT licence
-# ____________________________________________________________
-"enables us to carry out a calculation and return an option's value"
-from error import DisabledOptionError, SpecialOwnersError
-# ____________________________________________________________
-# automatic Option object
-special_owners = ['auto', 'fill']
-                  
-def special_owner_factory(name, owner, value, 
-                          callback, callback_params=None, config=None):
-    # in case of an 'auto' and a 'fill' without a value, 
-    # we have to carry out a calculation
-    return calc_factory(name, callback, callback_params, config)
-
-def calc_factory(name, callback, callback_params, config):
-    # FIXME we have to know the exact status of the config 
-    # not to disrupt it
-    # config.freeze()
-    if callback_params is None:
-        callback_params = {}
-    tcparams = {}
-    one_is_multi = False
-    len_multi = 0
-    for key, value in callback_params.items():
-        if type(value) == tuple:
-            path, check_disabled = value
-            try:
-                opt_value = getattr(config, path)
-                opt = config.unwrap_from_path(path)
-            except DisabledOptionError, e:
-                if chek_disabled:
-                    continue
-                raise DisabledOptionError(e)
-            is_multi = opt.is_multi()
-            if is_multi:
-                if opt_value != None:
-                    len_value = len(opt_value)
-                    if len_multi != 0 and len_multi != len_value:
-                        raise SpecialOwnersError('unable to carry out a calculation, '
-                        'option values with multi types must have same length for: '
-                         + name)
-                    len_multi = len_value
-                one_is_multi = True
-            tcparams[key] = (opt_value, is_multi)
-        else:
-            tcparams[key] = (value, False)
-
-    if one_is_multi:
-        ret = []
-        for incr in range(len_multi):
-            tcp = {}
-            for key, couple in tcparams.items():
-                value, ismulti = couple
-                if ismulti and value != None:
-                    tcp[key] = value[incr]
-                else:
-                    tcp[key] = value
-            ret.append(calculate(name, callback, tcp))
-        return ret
-    else:
-        tcp = {}
-        for key, couple in tcparams.items():
-            tcp[key] = couple[0]
-        return calculate(name, callback, tcp)
-    
-def calculate(name, callback, tcparams):
-    try:
-        # XXX not only creole...
-        from creole import eosfunc
-        return getattr(eosfunc, callback)(**tcparams) 
-    except AttributeError, err:
-        import traceback
-        traceback.print_exc()
-        raise SpecialOwnersError("callback: {0} return error {1} for "
-                       "option: {2}".format(callback, str(err), name))
-
diff --git a/src/basetype.py b/src/basetype.py
deleted file mode 100644 (file)
index c501ccb..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8 -*-
-"base 'interface' types for option types"
-# Copyright (C) 2012 Team tiramisu (see README for all contributors)
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-# The original `Config` design model is unproudly borrowed from 
-# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
-# the whole pypy projet is under MIT licence
-# ____________________________________________________________
-# Option and OptionDescription modes
-modes = ['normal', 'expert']
-
-class HiddenBaseType(object):
-    hidden = False
-    def hide(self):
-        self.hidden = True
-    def show(self):
-        self.hidden = False
-    def _is_hidden(self):
-        # dangerous method: how an Option can determine its status by itself ? 
-        return self.hidden
-
-class DisabledBaseType(object):
-    disabled = False   
-    def disable(self):
-        self.disabled = True
-    def enable(self):
-        self.disabled = False
-    def _is_disabled(self):
-        return self.disabled
-
-class ModeBaseType(object):
-    mode = 'normal'
-    def get_mode(self):
-        return self.mode
-    def set_mode(self, mode):
-        if mode not in modes:
-            raise TypeError("Unknown mode: {0}".format(mode))
-        self.mode = mode    
-
diff --git a/src/config.py b/src/config.py
deleted file mode 100644 (file)
index 851ac77..0000000
+++ /dev/null
@@ -1,540 +0,0 @@
-# -*- coding: utf-8 -*-
-"pretty small and local configuration management tool"
-# Copyright (C) 2012 Team tiramisu (see README for all contributors)
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-# The original `Config` design model is unproudly borrowed from 
-# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
-# the whole pypy projet is under MIT licence
-# ____________________________________________________________
-from error import (HiddenOptionError, ConfigError, NotFoundError, 
-                AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, 
-                            SpecialOwnersError, MandatoryError, MethodCallError, 
-                                           DisabledOptionError, ModeOptionError)
-from option import (OptionDescription, Option, SymLinkOption, group_types, 
-                    Multi, apply_requires, modes)
-from autolib import special_owners, special_owner_factory
-from copy import copy
-# ______________________________________________________________________
-# generic owner. 'default' is the general config owner after init time
-default_owner = 'user'
-# ____________________________________________________________
-class Config(object):
-    _cfgimpl_hidden = True
-    _cfgimpl_disabled = True
-    _cfgimpl_mandatory = True
-    _cfgimpl_frozen = False
-    _cfgimpl_owner = default_owner
-    _cfgimpl_toplevel = None
-    _cfgimpl_mode = 'normal'
-    
-    def __init__(self, descr, parent=None, **overrides):
-        self._cfgimpl_descr = descr
-        self._cfgimpl_value_owners = {}
-        self._cfgimpl_parent = parent
-        # `Config()` indeed takes care of the `Option()`'s values
-        self._cfgimpl_values = {}
-        self._cfgimpl_previous_values = {}
-        # XXX warnings are a great idea, let's make up a better use of it 
-        self._cfgimpl_warnings = []
-        self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
-        # `freeze()` allows us to carry out this calculation again if necessary 
-        self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen 
-        self._cfgimpl_build(overrides)
-
-    def _validate_duplicates(self, children):
-        duplicates = []
-        for dup in children:
-            if dup._name not in duplicates:
-                duplicates.append(dup._name)
-            else:
-                raise ConflictConfigError('duplicate option name: ' 
-                    '{0}'.format(dup._name))
-
-    def _cfgimpl_build(self, overrides):
-        self._validate_duplicates(self._cfgimpl_descr._children)
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, Option):
-                if child.is_multi():
-                    childdef = Multi(copy(child.getdefault()), config=self, 
-                                     child=child)
-                    self._cfgimpl_values[child._name] = childdef
-                    self._cfgimpl_previous_values[child._name] = list(childdef)
-                else:
-                    childdef = child.getdefault()
-                    self._cfgimpl_values[child._name] = childdef
-                    self._cfgimpl_previous_values[child._name] = childdef 
-                if child.getcallback() is not None:
-                    if child._is_hidden():
-                        self._cfgimpl_value_owners[child._name] = 'auto'
-                    else:
-                        self._cfgimpl_value_owners[child._name] = 'fill'
-                else:
-                    if child.is_multi():
-                        self._cfgimpl_value_owners[child._name] = ['default' \
-                            for i in range(len(child.getdefault() ))]
-                    else:
-                        self._cfgimpl_value_owners[child._name] = 'default'
-            elif isinstance(child, OptionDescription):
-                self._validate_duplicates(child._children)
-                self._cfgimpl_values[child._name] = Config(child, parent=self)
-        self.override(overrides)
-
-    def cfgimpl_update(self):
-        "dynamically adds `Option()` or `OptionDescription()`"
-        # Nothing is static. Everything evolve.
-        # FIXME this is an update for new options in the schema only 
-        # see the update_child() method of the descr object 
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, Option):
-                if child._name not in self._cfgimpl_values:
-                    if child.is_multi():
-                        self._cfgimpl_values[child._name] = Multi(
-                                copy(child.getdefault()), config=self, child=child)
-                        self._cfgimpl_value_owners[child._name] = ['default' \
-                                for i in range(len(child.getdefault() ))]
-                    else:
-                        self._cfgimpl_values[child._name] = copy(child.getdefault())
-                        self._cfgimpl_value_owners[child._name] = 'default'
-            elif isinstance(child, OptionDescription):
-                if child._name not in self._cfgimpl_values:
-                    self._cfgimpl_values[child._name] = Config(child, parent=self)
-
-    def override(self, overrides):
-        for name, value in overrides.iteritems():
-            homeconfig, name = self._cfgimpl_get_home_by_path(name)
-            #  if there are special_owners, impossible to override
-            if homeconfig._cfgimpl_value_owners[name] in special_owners:
-                raise SpecialOwnersError("cannot override option: {0} because "
-                                            "of its special owner".format(name))
-            homeconfig.setoption(name, value, 'default')
-
-    def cfgimpl_set_owner(self, owner):
-        self._cfgimpl_owner = owner
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, OptionDescription):
-                self._cfgimpl_values[child._name].cfgimpl_set_owner(owner)
-    # ____________________________________________________________
-    def cfgimpl_hide(self):
-        if self._cfgimpl_parent != None:
-            raise MethodCallError("this method root_hide() shall not be"
-                                           "used with non-root Config() object") 
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_hidden = True
-
-    def cfgimpl_show(self):
-        if self._cfgimpl_parent != None:
-            raise MethodCallError("this method root_hide() shall not be"
-                                           "used with non-root Config() object") 
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_hidden = False
-    # ____________________________________________________________
-    def cfgimpl_disable(self):
-        if self._cfgimpl_parent != None:
-            raise MethodCallError("this method root_hide() shall not be"
-                                           "used with non-root Confit() object") 
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_disabled = True
-
-    def cfgimpl_enable(self):
-        if self._cfgimpl_parent != None:
-            raise MethodCallError("this method root_hide() shall not be"
-                                           "used with non-root Confit() object") 
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_disabled = False
-    # ____________________________________________________________
-    def __setattr__(self, name, value):
-        if '.' in name:
-            homeconfig, name = self._cfgimpl_get_home_by_path(name)
-            return setattr(homeconfig, name, value)
-
-        if name.startswith('_cfgimpl_'):
-            self.__dict__[name] = value
-            return
-        if self._cfgimpl_frozen and getattr(self, name) != value:
-            raise TypeError("trying to change a value in a frozen config"
-                                                ": {0} {1}".format(name, value))
-        if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
-            self._validate(name, getattr(self._cfgimpl_descr, name))
-        self.setoption(name, value, self._cfgimpl_owner)
-        
-    def _validate(self, name, opt_or_descr):
-        apply_requires(opt_or_descr, self) 
-        if not type(opt_or_descr) == OptionDescription:
-            # hidden options
-            if self._cfgimpl_toplevel._cfgimpl_hidden and \
-                (opt_or_descr._is_hidden() or self._cfgimpl_descr._is_hidden()):
-                raise HiddenOptionError("trying to access to a hidden option:"
-                                                           " {0}".format(name))
-            # disabled options
-            if self._cfgimpl_toplevel._cfgimpl_disabled and \
-            (opt_or_descr._is_disabled() or self._cfgimpl_descr._is_disabled()):
-                raise DisabledOptionError("this option is disabled:"
-                                                            " {0}".format(name))
-            # expert options 
-            # XXX currently doesn't look at the group, is it really necessary ?
-            if self._cfgimpl_toplevel._cfgimpl_mode != 'normal':
-                if opt_or_descr.get_mode() != 'normal':
-                    raise ModeOptionError("this option's mode is not normal:"
-                                                            " {0}".format(name))
-
-    def __getattr__(self, name):
-        # attribute access by passing a path, 
-        # for instance getattr(self, "creole.general.family.adresse_ip_eth0") 
-        if '.' in name:
-            homeconfig, name = self._cfgimpl_get_home_by_path(name)
-            return getattr(homeconfig, name)
-        opt_or_descr = getattr(self._cfgimpl_descr, name)
-        # symlink options 
-        if type(opt_or_descr) == SymLinkOption:
-            return getattr(self, opt_or_descr.path)
-        self._validate(name, opt_or_descr)
-        # special attributes
-        if name.startswith('_cfgimpl_'):
-            # if it were in __dict__ it would have been found already
-            return self.__dict__[name]
-            raise AttributeError("%s object has no attribute %s" %
-                                 (self.__class__, name))
-        if name not in self._cfgimpl_values:
-            raise AttributeError("%s object has no attribute %s" %
-                                 (self.__class__, name))
-        if name in self._cfgimpl_value_owners:
-            owner = self._cfgimpl_value_owners[name]
-            if owner in special_owners:
-                value = self._cfgimpl_values[name]
-                if value != None:
-                    if opt_or_descr.is_multi():
-                        if owner == 'fill' and None not in value:
-                            return value
-                    else:
-                        if owner == 'fill' and value != None:
-                            return value
-                result = special_owner_factory(name, owner, 
-                            value=value,
-                            callback=opt_or_descr.getcallback(),
-                            callback_params=opt_or_descr.getcallback_params(),
-                            config=self._cfgimpl_get_toplevel())
-                # this result **shall not** be a list 
-                # for example, [1, 2, 3, None] -> [1, 2, 3, result]
-                if isinstance(result, list):
-                    raise ConfigError('invalid calculated value returned'
-                        ' for option {0} : shall not be a list'.format(name))
-                if result != None and not opt_or_descr._validate(result):
-                    raise ConfigError('invalid calculated value returned'
-                        ' for option {0}'.format(name))
-                if opt_or_descr.is_multi():
-                    if value == []:
-                        _result = Multi([result], value.config, value.child)
-                    else:
-                        _result = Multi([], value.config, value.child)
-                        for val in value:
-                            if val == None:
-                                val = result
-                            _result.append(val)
-                else:
-                    _result = result
-                return _result
-        # mandatory options
-        if not isinstance(opt_or_descr, OptionDescription):
-            homeconfig = self._cfgimpl_get_toplevel()
-            mandatory = homeconfig._cfgimpl_mandatory
-            if opt_or_descr.is_mandatory() and mandatory:
-                if self._cfgimpl_values[name] == None\
-                  and opt_or_descr.getdefault() == None:
-                    raise MandatoryError("option: {0} is mandatory " 
-                                          "and shall have a value".format(name))
-        return self._cfgimpl_values[name]
-                
-    def __dir__(self):
-        #from_type = dir(type(self))
-        from_dict = list(self.__dict__)
-        extras = list(self._cfgimpl_values)
-        return sorted(set(extras + from_dict))
-
-    def unwrap_from_name(self, name):
-        # didn't have to stoop so low: `self.get()` must be the proper method
-        # **and it is slow**: it recursively searches into the namespaces
-        paths = self.getpaths(allpaths=True)
-        opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
-        all_paths = [p.split(".") for p in self.getpaths()]
-        for pth in all_paths:
-            if name in pth:
-                return opts[".".join(pth)]
-        raise NotFoundError("name: {0} not found".format(name))
-        
-    def unwrap_from_path(self, path):
-        # didn't have to stoop so low, `geattr(self, path)` is much better
-        # **fast**: finds the option directly in the appropriate namespace
-        if '.' in path:
-            homeconfig, path = self._cfgimpl_get_home_by_path(path)
-            return getattr(homeconfig._cfgimpl_descr, path)
-        return getattr(self._cfgimpl_descr, path)
-                                            
-    def __delattr__(self, name):
-        # if you use delattr you are responsible for all bad things happening
-        if name.startswith('_cfgimpl_'):
-            del self.__dict__[name]
-            return
-        self._cfgimpl_value_owners[name] = 'default'
-        opt = getattr(self._cfgimpl_descr, name)
-        if isinstance(opt, OptionDescription):
-            raise AttributeError("can't option subgroup")
-        self._cfgimpl_values[name] = getattr(opt, 'default', None)
-
-    def setoption(self, name, value, who=None):
-        #who is **not necessarily** a owner, because it cannot be a list
-        #FIXME : sortir le setoption pour les multi, ca ne devrait pas être la
-        child = getattr(self._cfgimpl_descr, name)
-        if who == None:
-            if child.is_multi():
-                newowner = [self._cfgimpl_owner for i in range(len(value))] 
-            else:
-                newowner = self._cfgimpl_owner
-        else:
-            if type(child) != SymLinkOption:
-                if child.is_multi():
-                    if type(value) != Multi:
-                        if type(value) == list:
-                            value = Multi(value, self, child)
-                        else:
-                            raise ConfigError("invalid value for option:"
-                                       " {0} that is set to multi".format(name))
-                    newowner = [who for i in range(len(value))]
-                else:
-                    newowner = who 
-        if type(child) != SymLinkOption:
-            if name not in self._cfgimpl_values:
-                raise AttributeError('unknown option %s' % (name,))
-            # special owners, a value with a owner *auto* cannot be changed
-            oldowner = self._cfgimpl_value_owners[child._name]
-            if oldowner == 'auto':
-                if who == 'auto':
-                    raise ConflictConfigError('cannot override value to %s for '
-                                          'option %s' % (value, name))
-            if oldowner == who:
-                oldvalue = getattr(self, name)
-                if oldvalue == value: #or who in ("default",):
-                    return
-            child.setoption(self, value, who)
-            # if the value owner is 'auto', set the option to hidden 
-            if who == 'auto':
-                if not child._is_hidden():
-                    child.hide()
-            if (value is None and who != 'default' and not child.is_multi()):
-                child.setowner(self, 'default')
-                self._cfgimpl_values[name] = copy(child.getdefault())
-            elif (value == [] and who != 'default' and child.is_multi()):
-                child.setowner(self, ['default' for i in range(len(child.getdefault()))])
-                self._cfgimpl_values[name] = Multi(copy(child.getdefault()),
-                            config=self, child=child)
-            else:         
-                child.setowner(self, newowner)
-        else:
-            homeconfig = self._cfgimpl_get_toplevel()
-            child.setoption(homeconfig, value, who)
-
-    def set(self, **kwargs):
-        all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
-        for key, value in kwargs.iteritems():
-            key_p = key.split('.')
-            candidates = [p for p in all_paths if p[-len(key_p):] == key_p]
-            if len(candidates) == 1:
-                name = '.'.join(candidates[0])
-                homeconfig, name = self._cfgimpl_get_home_by_path(name)
-                try:
-                    getattr(homeconfig, name)
-                except MandatoryError:
-                    pass
-                except Exception, e:
-                    raise e # HiddenOptionError or DisabledOptionError
-                homeconfig.setoption(name, value, self._cfgimpl_owner)
-            elif len(candidates) > 1:
-                raise AmbigousOptionError(
-                    'more than one option that ends with %s' % (key, ))
-            else:
-                raise NoMatchingOptionFound(
-                    'there is no option that matches %s' 
-                    ' or the option is hidden or disabled'% (key, ))
-
-    def get(self, name):
-        paths = self.getpaths(allpaths=True)
-        pathsvalues = []
-        for path in paths:
-            pathname = path.split('.')[-1]
-            if pathname == name:
-                try:
-                    value = getattr(self, path)            
-                    return value 
-                except Exception, e:
-                    raise e
-        raise NotFoundError("option {0} not found in config".format(name))                    
-
-    def _cfgimpl_get_home_by_path(self, path):
-        """returns tuple (config, name)"""
-        path = path.split('.')
-        
-        for step in path[:-1]:
-            self = getattr(self, step)
-        return self, path[-1]
-
-    def _cfgimpl_get_toplevel(self):
-        while self._cfgimpl_parent is not None:
-            self = self._cfgimpl_parent
-        return self
-
-    def cfgimpl_previous_value(self, path):
-        home, name = self._cfgimpl_get_home_by_path(path)
-        return home._cfgimpl_previous_values[name]
-    
-    def get_previous_value(self, name):
-        return self._cfgimpl_previous_values[name]
-             
-    def add_warning(self, warning):
-        self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
-
-    def get_warnings(self):
-        return self._cfgimpl_get_toplevel()._cfgimpl_warnings
-    # ____________________________________________________________
-    # freeze and read-write statuses
-    def cfgimpl_freeze(self):
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_frozen = True
-        self._cfgimpl_frozen = True
-
-    def cfgimpl_unfreeze(self):
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_frozen = False
-        self._cfgimpl_frozen = False
-
-    def is_frozen(self):
-        # it should be the same value as self._cfgimpl_frozen...
-        rootconfig = self._cfgimpl_get_toplevel()
-        return rootconfig.__dict__['_cfgimpl_frozen']
-
-    def cfgimpl_read_only(self):
-        # hung up on freeze, hidden and disabled concepts 
-        self.cfgimpl_freeze()
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_hidden = False
-        rootconfig._cfgimpl_disabled = True
-        rootconfig._cfgimpl_mandatory = True
-
-    def cfgimpl_set_mode(self, mode):
-        # normal or expert mode
-        rootconfig = self._cfgimpl_get_toplevel()
-        if mode not in modes:
-            raise ConfigError("mode {0} not available".format(mode))
-        rootconfig._cfgimpl_mode = mode
-    
-    def cfgimpl_read_write(self):
-        # hung up on freeze, hidden and disabled concepts
-        self.cfgimpl_unfreeze()
-        rootconfig = self._cfgimpl_get_toplevel()
-        rootconfig._cfgimpl_hidden = True
-        rootconfig._cfgimpl_disabled = True
-        rootconfig._cfgimpl_mandatory = False
-    # ____________________________________________________________
-    def getkey(self):
-        return self._cfgimpl_descr.getkey(self)
-
-    def __hash__(self):
-        return hash(self.getkey())
-
-    def __eq__(self, other):
-        return self.getkey() == other.getkey()
-
-    def __ne__(self, other):
-        return not self == other
-
-    def __iter__(self):
-        # iteration only on Options (not OptionDescriptions)
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, Option):
-                try:
-                    yield child._name, getattr(self, child._name)
-                except:
-                    pass # hidden, disabled option group
-
-    def iter_groups(self, group_type=None):
-        "iteration on OptionDescriptions"
-        if group_type == None:
-            groups = group_types
-        else:
-            if group_type not in group_types:
-                raise TypeError("Unknown group_type: {0}".format(group_type))
-            groups = [group_type]
-        for child in self._cfgimpl_descr._children:
-            if isinstance(child, OptionDescription):
-                    try:
-                        if child.get_group_type() in groups: 
-                            yield child._name, getattr(self, child._name)
-                    except:
-                        pass # hidden, disabled option
-                    
-    def __str__(self, indent=""):
-        lines = []
-        children = [(child._name, child)
-                    for child in self._cfgimpl_descr._children]
-        children.sort()
-        for name, child in children:
-            if self._cfgimpl_value_owners.get(name, None) == 'default':
-                continue
-            value = getattr(self, name)
-            if isinstance(value, Config):
-                substr = value.__str__(indent + "    ")
-            else:
-                substr = "%s    %s = %s" % (indent, name, value)
-            if substr:
-                lines.append(substr)
-        if indent and not lines:
-            return ''   # hide subgroups with all default values
-        lines.insert(0, "%s[%s]" % (indent, self._cfgimpl_descr._name,))
-        return '\n'.join(lines)
-
-    def getpaths(self, include_groups=False, allpaths=False):
-        """returns a list of all paths in self, recursively, taking care of 
-        the context (hidden/disabled)
-        """
-        paths = []
-        for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
-            try: 
-                value = getattr(self, path)
-            except Exception, e:
-                if not allpaths:
-                    pass # hidden or disabled option
-                else:
-                    paths.append(path) # hidden or disabled option added
-            else:
-                paths.append(path)
-        return paths 
-        
-def make_dict(config, flatten=False):
-    paths = config.getpaths()
-    pathsvalues = []
-    for path in paths:
-        if flatten:
-            pathname = path.split('.')[-1]
-        else:
-            pathname = path
-        try:
-            value = getattr(config, path)            
-            pathsvalues.append((pathname, value))      
-        except:
-            pass # this just a hidden or disabled option  
-    options = dict(pathsvalues)
-    return options
-# ____________________________________________________________
-
diff --git a/src/error.py b/src/error.py
deleted file mode 100644 (file)
index 4588f71..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-class AmbigousOptionError(Exception):
-    pass
-class NoMatchingOptionFound(AttributeError):
-    pass
-class ConfigError(Exception):
-    pass
-class ConflictConfigError(ConfigError):
-    pass
-class HiddenOptionError(AttributeError):
-    pass
-class DisabledOptionError(AttributeError):
-    pass 
-class NotFoundError(Exception):
-    pass
-class MethodCallError(Exception):
-    pass 
-class RequiresError(Exception):
-    pass    
-class MandatoryError(Exception):
-    pass 
-class SpecialOwnersError(Exception):
-    pass 
-class ModeOptionError(Exception):
-    pass
-    
diff --git a/src/option.py b/src/option.py
deleted file mode 100644 (file)
index d6de739..0000000
+++ /dev/null
@@ -1,520 +0,0 @@
-# -*- coding: utf-8 -*-
-"option types and option description for the configuration management"
-# Copyright (C) 2012 Team tiramisu (see README for all contributors)
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-# The original `Config` design model is unproudly borrowed from 
-# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
-# the whole pypy projet is under MIT licence
-# ____________________________________________________________
-from autolib import special_owners
-from basetype import HiddenBaseType, DisabledBaseType, ModeBaseType, modes
-from error import (ConfigError, ConflictConfigError, NotFoundError, 
-                   SpecialOwnersError, RequiresError)
-available_actions = ['hide', 'show', 'enable', 'disable'] 
-reverse_actions = {'hide': 'show', 'show': 'hide', 
-                   'disable':'enable', 'enable': 'disable'}
-# ____________________________________________________________
-# OptionDescription authorized group_type values
-group_types = ['default', 'family', 'group', 'master']
-# multi types 
-
-class Multi(list):
-    "container that support items for the values of list (multi) options" 
-    def __init__(self, lst, config, child):
-        self.config = config
-        self.child = child
-        super(Multi, self).__init__(lst)
-        
-    def __setitem__(self, key, value):
-        return self.setoption(value, key)
-
-    def append(self, value):
-        self.setoption(value)
-   
-    def setoption(self, value, key=None):
-        owners = self.child.getowner(self.config)
-        # None is replaced by default_multi
-        if value == None:
-            defval = self.child.getdefault()
-            if key is not None and len(defval) > key:
-                value = defval[key]
-            else:
-                value = self.child.default_multi
-            who = 'default'
-        else:
-            who = self.config._cfgimpl_owner
-            if not self.child._validate(value):
-                raise ConfigError("invalid value {0} "
-                    "for option {1}".format(str(value), self.child._name))
-        oldvalue = list(self)
-        oldowner = self.child.getowner(self.config)
-        if key is None:
-            ret = super(Multi, self).append(value)
-            oldvalue.append(None)
-            oldowner.append(who)
-        else:
-            ret = super(Multi, self).__setitem__(key, value)
-            oldowner[key] = who
-        self.config._cfgimpl_previous_values[self.child._name] = oldvalue
-        self.child.setowner(self.config, oldowner)
-        return ret
-        
-    def pop(self, key):
-        oldowner = self.child.getowner(self.config)
-        oldowner.pop(key)
-        self.child.setowner(self.config, oldowner)
-        super(Multi, self).pop(key)
-# ____________________________________________________________
-#
-class Option(HiddenBaseType, DisabledBaseType, ModeBaseType):
-    #reminder: an Option object is **not** a container for the value
-    _frozen = False
-    def __init__(self, name, doc, default=None, default_multi=None, 
-                 requires=None, mandatory=False, multi=False, callback=None, 
-                 callback_params=None, mode='normal'):
-        self._name = name
-        self.doc = doc
-        self._requires = requires
-        self._mandatory = mandatory
-        self.multi = multi
-        if not self.multi and default_multi is not None:
-            raise ConfigError("a default_multi is set whereas multi is False"
-                  " in option: {0}".format(name)) 
-        if default_multi is not None and not self._validate(default_multi):
-            raise ConfigError("invalid default_multi value {0} "
-                "for option {1}".format(str(default_multi), name))
-        self.default_multi = default_multi
-        #if self.multi and default_multi is None:
-            # _cfgimpl_warnings[name] = DefaultMultiWarning   
-        if callback is not None and (default is not None or default_multi is not None):
-            raise ConfigError("defaut values not allowed if option: {0}" 
-                "is calculated".format(name))
-        self.callback = callback
-        if self.callback is None and callback_params is not None:
-            raise ConfigError("params defined for a callback function but"
-            " no callback defined yet for option {0}".format(name)) 
-        self.callback_params = callback_params
-        if mode not in modes:
-            raise ConfigError("mode {0} not available".format(mode))
-        self.mode = mode
-        if self.multi == True:
-            if default == None:
-                default = []
-            if not isinstance(default, list) or not self.validate(default):
-                raise ConfigError("invalid default value {0} "
-                "for option {1} : not list type".format(str(default), name))
-        else:
-            if default != None and not self.validate(default):
-                raise ConfigError("invalid default value {0} " 
-                                         "for option {1}".format(str(default), name))
-        self.default = default
-        
-    def validate(self, value):
-        if self.multi == False:
-            # None allows the reset of the value
-            if value != None:
-                return self._validate(value)
-        else:
-            if not isinstance(value, list): 
-                raise ConfigError("invalid value {0} " 
-                        "for option {1} which must be a list".format(value,
-                        self._name))
-            for val in value:
-                if val != None:
-                    # None allows the reset of the value
-                    if not self._validate(val):
-                        return False
-        return True
-
-    def getdefault(self):
-        return self.default
-
-    def getdoc(self):
-        return self.doc
-
-    def getcallback(self):
-        return self.callback
-
-    def getcallback_params(self):
-        return self.callback_params
-
-    def setowner(self, config, owner):
-        # config *must* be only the **parent** config (not the toplevel config) 
-        # owner is a **real* owner, a list is actually allowable here
-        name = self._name
-        if self._frozen:
-            raise TypeError("trying to change a frozen option's owner: %s" % name)
-        if owner in special_owners:
-            if self.callback == None:
-                raise SpecialOwnersError("no callback specified for" 
-                                                      "option {0}".format(name))
-        if self.is_multi():
-            if not type(owner) == list:
-                raise ConfigError("invalid owner for multi "
-                    "option: {0}".format(self._name))
-        config._cfgimpl_value_owners[name] = owner
-
-    def getowner(self, config):
-        # config *must* be only the **parent** config (not the toplevel config) 
-        return config._cfgimpl_value_owners[self._name]
-
-    def setoption(self, config, value, who):
-        "who is **not necessarily** a owner because it cannot be a list"
-        name = self._name
-        if self._frozen:
-            raise TypeError('trying to change a frozen option object: %s' % name)
-        # we want the possibility to reset everything
-        if who == "default" and value is None:
-            self.default = None
-            return
-        if not self.validate(value):
-            raise ConfigError('invalid value %s for option %s' % (value, name))
-        if who == "default":
-            # changes the default value (and therefore resets the previous value)
-            if self._validate(value):
-                self.default = value
-            else:
-                raise ConfigError("invalid value %s for option %s" % (value, name))
-        apply_requires(self, config)
-        # FIXME put the validation for the multi somewhere else
-#            # it is a multi **and** it has requires
-#            if self.multi == True:
-#                if type(value) != list:
-#                    raise TypeError("value {0} must be a list".format(value))
-#                if self._requires is not None:
-#                    for reqname in self._requires:
-#                        # FIXME : verify that the slaves are all multi
-#                        #option = getattr(config._cfgimpl_descr, reqname)
-#    #                    if not option.multi == True:
-#    #                        raise ConflictConfigError("an option with requires "
-#    #                         "has to be a list type : {0}".format(name)) 
-#                        if len(config._cfgimpl_values[reqname]) != len(value):
-#                            raise ConflictConfigError("an option with requires "
-#                             "has not the same length of the others " 
-#                             "in the group : {0}".format(reqname)) 
-        if type(config._cfgimpl_values[name]) == Multi:
-            config._cfgimpl_previous_values[name] = list(config._cfgimpl_values[name])
-        else:
-            config._cfgimpl_previous_values[name] = config._cfgimpl_values[name] 
-        config._cfgimpl_values[name] = value
-
-    def getkey(self, value):
-        return value
-
-    def freeze(self):
-        self._frozen = True
-        return True
-
-    def unfreeze(self):
-        self._frozen = False
-    # ____________________________________________________________
-    def is_multi(self):
-        return self.multi
-
-    def is_mandatory(self):
-        return self._mandatory
-        
-class ChoiceOption(Option):
-    opt_type = 'string'
-    
-    def __init__(self, name, doc, values, open_values=False, default=None,
-                 requires=None, callback=None, callback_params=None,
-                 multi=False, mandatory=False):
-        self.values = values
-        if open_values not in [True, False]:
-            raise ConfigError('Open_values must be a boolean for '
-                              '{0}'.format(name))
-        self.open_values = open_values
-        super(ChoiceOption, self).__init__(name, doc, default=default,
-                           callback=callback, callback_params=callback_params, 
-                           requires=requires, multi=multi, mandatory=mandatory)
-
-    def setoption(self, config, value, who):
-        name = self._name
-        super(ChoiceOption, self).setoption(config, value, who)
-
-    def _validate(self, value):
-        if not self.open_values:
-            return value is None or value in self.values
-        else:
-            return True
-
-class BoolOption(Option):
-    opt_type = 'bool'
-    
-#    def __init__(self, name, doc, default=None, requires=None,
-#                                  validator=None, multi=False, mandatory=False):
-#        super(BoolOption, self).__init__(name, doc, default=default, 
-#                            requires=requires, multi=multi, mandatory=mandatory)
-        #self._validator = validator
-
-    def _validate(self, value):
-        return isinstance(value, bool)
-
-# FIXME config level validator             
-#    def setoption(self, config, value, who):
-#        name = self._name
-#        if value and self._validator is not None:
-#            toplevel = config._cfgimpl_get_toplevel()
-#            self._validator(toplevel)
-#        super(BoolOption, self).setoption(config, value, who)
-
-class IntOption(Option):
-    opt_type = 'int'
-    
-    def _validate(self, value):
-        try:
-            int(value)
-        except TypeError:
-            return False
-        return True
-                            
-    def setoption(self, config, value, who):
-        try:
-            super(IntOption, self).setoption(config, value, who)
-        except TypeError, e:
-            raise ConfigError(*e.args)
-
-class FloatOption(Option):
-    opt_type = 'float'
-
-    def _validate(self, value):
-        try:
-            float(value)
-        except TypeError:
-            return False
-        return True
-
-    def setoption(self, config, value, who):
-        try:
-            super(FloatOption, self).setoption(config, float(value), who)
-        except TypeError, e:
-            raise ConfigError(*e.args)
-
-class StrOption(Option):
-    opt_type = 'string'
-    
-    def _validate(self, value):
-        return isinstance(value, str)
-                                     
-    def setoption(self, config, value, who):
-        try:
-            super(StrOption, self).setoption(config, value, who)
-        except TypeError, e:
-            raise ConfigError(*e.args)
-
-class SymLinkOption(object): #(HiddenBaseType, DisabledBaseType):
-    opt_type = 'symlink'
-    
-    def __init__(self, name, path):
-        self._name = name
-        self.path = path 
-    
-    def setoption(self, config, value, who):
-        try:
-            setattr(config, self.path, value) # .setoption(self.path, value, who)
-        except TypeError, e:
-            raise ConfigError(*e.args)
-
-class IPOption(Option):
-    opt_type = 'ip'
-    
-    def _validate(self, value):
-        # by now the validation is nothing but a string, use IPy instead
-        return isinstance(value, str)
-                                     
-    def setoption(self, config, value, who):
-        try:
-            super(IPOption, self).setoption(config, value, who)
-        except TypeError, e:
-            raise ConfigError(*e.args)
-
-class NetmaskOption(Option):
-    opt_type = 'netmask'
-    
-    def _validate(self, value):
-        # by now the validation is nothing but a string, use IPy instead
-        return isinstance(value, str)
-                                     
-    def setoption(self, config, value, who):
-        try:
-            super(NetmaskOption, self).setoption(config, value, who)
-        except TypeError, e:
-            raise ConfigError(*e.args)
-
-class ArbitraryOption(Option):
-    def __init__(self, name, doc, default=None, defaultfactory=None, 
-                                   requires=None, multi=False, mandatory=False):
-        super(ArbitraryOption, self).__init__(name, doc, requires=requires,
-                                               multi=multi, mandatory=mandatory)
-        self.defaultfactory = defaultfactory
-        if defaultfactory is not None:
-            assert default is None
-
-    def _validate(self, value):
-        return True
-
-    def getdefault(self):
-        if self.defaultfactory is not None:
-            return self.defaultfactory()
-        return self.default
-
-class OptionDescription(HiddenBaseType, DisabledBaseType, ModeBaseType):
-    group_type = 'default'
-    
-    def __init__(self, name, doc, children, requires=None):
-        self._name = name
-        self.doc = doc
-        self._children = children
-        self._requires = requires
-        self._build()
-    
-    def getdoc(self):
-        return self.doc
-
-    def _build(self):
-        for child in self._children:
-            setattr(self, child._name, child)
-
-    def add_child(self, child):
-        "dynamically adds a configuration option"
-        #Nothing is static. Even the Mona Lisa is falling apart.
-        for ch in self._children:
-            if isinstance(ch, Option):
-                if child._name == ch._name:
-                    raise ConflictConfigError("existing option : {0}".format(
-                                                                   child._name))
-        self._children.append(child)
-        setattr(self, child._name, child)
-    
-    def update_child(self, child):
-        "modification of an existing option"
-        # XXX : corresponds to the `redefine`, is it usefull 
-        pass
-        
-    def getkey(self, config):
-        return tuple([child.getkey(getattr(config, child._name))
-                      for child in self._children])
-
-    def getpaths(self, include_groups=False, currpath=None):
-        """returns a list of all paths in self, recursively
-           currpath should not be provided (helps with recursion)
-        """
-        if currpath is None:
-            currpath = []
-        paths = []
-        for option in self._children:
-            attr = option._name
-            if attr.startswith('_cfgimpl'):
-                continue
-            value = getattr(self, attr)
-            if isinstance(value, OptionDescription):
-                if include_groups:
-                    paths.append('.'.join(currpath + [attr]))
-                currpath.append(attr)
-                paths += value.getpaths(include_groups=include_groups,
-                                        currpath=currpath)
-                currpath.pop()
-            else:
-                paths.append('.'.join(currpath + [attr]))
-        return paths
-    # ____________________________________________________________
-
-    def set_group_type(self, group_type):
-        if group_type in group_types:
-            self.group_type = group_type
-        else:
-            raise ConfigError('not allowed value for group_type : {0}'.format(
-                              group_type))
-    
-    def get_group_type(self):
-        return self.group_type
-    # ____________________________________________________________
-    def hide(self):
-        super(OptionDescription, self).hide()
-        # FIXME : AND THE SUBCHILDREN ? 
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.hide()
-    
-    def show(self):
-        # FIXME : AND THE SUBCHILDREN ?? 
-        super(OptionDescription, self).show()
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.show()
-    # ____________________________________________________________
-    def disable(self):
-        super(OptionDescription, self).disable()
-        # FIXME : AND THE SUBCHILDREN ? 
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.disable()
-    
-    def enable(self):
-        # FIXME : AND THE SUBCHILDREN ? 
-        super(OptionDescription, self).enable()
-        for child in self._children:
-            if isinstance(child, OptionDescription):
-                child.enable()
-# ____________________________________________________________
-def apply_requires(opt, config):
-    if hasattr(opt, '_requires'):
-        if opt._requires is not None:
-            # malformed requirements
-            rootconfig = config._cfgimpl_get_toplevel()
-            for req in opt._requires:
-                if not type(req) == tuple and len(req) in (3, 4):
-                    raise RequiresError("malformed requirements for option:"
-                                                   " {0}".format(opt._name))
-            # all actions **must** be identical
-            actions = [req[2] for req in opt._requires]
-            action = actions[0]
-            for act in actions:
-                if act != action:
-                    raise RequiresError("malformed requirements for option:"
-                                                   " {0}".format(opt._name))
-            # filters the callbacks
-            matches = False
-            for req in opt._requires:
-                if len(req) == 3:
-                    name, expected, action = req
-                    inverted = False
-                if len(req) == 4:
-                    name, expected, action, inverted = req
-                    if inverted == 'inverted':
-                        inverted = True
-                homeconfig, shortname = \
-                                      rootconfig._cfgimpl_get_home_by_path(name)
-                # FIXME: doesn't work with 'auto' or 'fill' yet 
-                # (copy the code from the __getattr__
-                if shortname in homeconfig._cfgimpl_values:
-                    value = homeconfig._cfgimpl_values[shortname]
-                    if (not inverted and value == expected) or \
-                            (inverted and value != expected):
-                        if action not in available_actions:
-                            raise RequiresError("malformed requirements"
-                                           " for option: {0}".format(opt._name))
-                        getattr(opt, action)() #.hide() or show() or...
-                        matches = True
-                else: # option doesn't exist ! should not happen...
-                    raise NotFoundError("required option not found: "
-                                                             "{0}".format(name))
-            # no callback has been triggered, then just reverse the action
-            if not matches:
-                getattr(opt, reverse_actions[action])()
-                
diff --git a/src/tool.py b/src/tool.py
deleted file mode 100644 (file)
index 9a31c45..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-# Copyright (C) 2012 Team tiramisu (see README for all contributors)
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
-# The original `Config` design model is unproudly borrowed from 
-# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
-# the whole pypy projet is under MIT licence
-from config import Config
-from option import (OptionDescription, Option, ChoiceOption, BoolOption, 
-                    FloatOption, StrOption, IntOption, IPOption, NetmaskOption, 
-                    ArbitraryOption, group_types, apply_requires)
-
-# ____________________________________________________________
-# reverse factory
-# XXX HAAAAAAAAAAAACK (but possibly a good one)
-def reverse_from_paths(data):
-    "rebuilds a (fake) data structure from an unflatten `make_dict()` result"
-    # ____________________________________________________________
-    _build_map = {
-        bool: BoolOption,
-        int: IntOption,
-        float: FloatOption,
-        str: StrOption,
-    }
-    def option_factory(name, value):
-        "dummy -> Option('dummy')"
-        if isinstance(value, list):
-            return _build_map[type(value[0])](name, '', multi=True, default=value)
-        else:            
-            return _build_map[type(value)](name, '', default=value)
-
-    def build_options(data):
-        "config.gc.dummy ->  Option('dummy')"
-        for key, value in data.items():
-            name = key.split('.')[-1]
-            yield (key, option_factory(name, value))
-    # ____________________________________________________________
-    def parent(pathname):
-        "config.gc.dummy -> config.gc"
-        if "." in pathname:
-            return ".".join(pathname.split('.')[:-1])
-        # no parent except rootconfig, naturally returns None    
-
-    def subgroups(pathname):
-        "config.gc.dummy.bool -> [config.gc, config.gc.dummy]"
-        group = parent(pathname)
-        parents =[]
-        while group is not None:
-            parents.append(group)
-            group = parent(group)
-        return parents 
-
-    def build_option_descriptions(data):
-        all_groups = []
-        for key in data.keys():
-            for group in subgroups(key):
-                # so group is unique in the list 
-                if group not in all_groups:
-                    all_groups.append(group)
-        for group in all_groups:
-            name = group.split('.')[-1]
-            yield (group, OptionDescription(name, '', []))
-    # ____________________________________________________________
-    descr = OptionDescription('tiramisu', 'fake rebuild structure', [])
-    cfg = Config(descr)
-    # add descrs in cfg
-    def compare(a, b):
-        l1 = a.split(".")
-        l2 = b.split(".")
-        if len(l1) < len(l2):
-            return -1
-        elif len(l1) > len(l2):
-            return 1
-        else:
-            return 0
-    grps = list(build_option_descriptions(data))
-    groups = dict(grps)
-    grp_paths = [pathname for pathname, opt_descr in grps]
-    grp_paths.sort(compare)
-    for grp in grp_paths:
-        if not "." in grp:
-            cfg._cfgimpl_descr.add_child(groups[grp])
-            cfg.cfgimpl_update()
-        else:
-            parentdescr = cfg.unwrap_from_path(parent(grp))
-            parentdescr.add_child(groups[grp])
-            getattr(cfg, parent(grp)).cfgimpl_update()
-    # add options in descrs
-    for pathname, opt in build_options(data):
-        current_group_name = parent(pathname)
-        if current_group_name == None:
-            cfg._cfgimpl_descr.add_child(opt)
-            cfg.cfgimpl_update()
-        else:
-            curr_grp = groups[current_group_name]
-            curr_grp.add_child(opt)
-            getattr(cfg, current_group_name).cfgimpl_update()
-
-    return cfg
-# ____________________________________________________________
-# extendable type
-class extend(type):
-    """
-    A magic trick for classes, which lets you add methods or attributes to a
-    class
-    """
-    def extend(cls, extclass):
-        bases = list(extclass.__bases__)
-        bases.append(extclass)
-        for cl in bases:
-            for key, value in cl.__dict__.items():
-                if key == '__module__':
-                    continue
-                setattr(cls, key, value)
-
-# ____________________________________________________________
-
diff --git a/tiramisu/__init__.py b/tiramisu/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py
new file mode 100644 (file)
index 0000000..febc172
--- /dev/null
@@ -0,0 +1,94 @@
+# Copyright (C) 2012 Team tiramisu (see README for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# The original `Config` design model is unproudly borrowed from 
+# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+"enables us to carry out a calculation and return an option's value"
+from tiramisu.error import DisabledOptionError, SpecialOwnersError
+# ____________________________________________________________
+# automatic Option object
+special_owners = ['auto', 'fill']
+                  
+def special_owner_factory(name, owner, value, 
+                          callback, callback_params=None, config=None):
+    # in case of an 'auto' and a 'fill' without a value, 
+    # we have to carry out a calculation
+    return calc_factory(name, callback, callback_params, config)
+
+def calc_factory(name, callback, callback_params, config):
+    # FIXME we have to know the exact status of the config 
+    # not to disrupt it
+    # config.freeze()
+    if callback_params is None:
+        callback_params = {}
+    tcparams = {}
+    one_is_multi = False
+    len_multi = 0
+    for key, value in callback_params.items():
+        if type(value) == tuple:
+            path, check_disabled = value
+            try:
+                opt_value = getattr(config, path)
+                opt = config.unwrap_from_path(path)
+            except DisabledOptionError, e:
+                if chek_disabled:
+                    continue
+                raise DisabledOptionError(e)
+            is_multi = opt.is_multi()
+            if is_multi:
+                if opt_value != None:
+                    len_value = len(opt_value)
+                    if len_multi != 0 and len_multi != len_value:
+                        raise SpecialOwnersError('unable to carry out a calculation, '
+                        'option values with multi types must have same length for: '
+                         + name)
+                    len_multi = len_value
+                one_is_multi = True
+            tcparams[key] = (opt_value, is_multi)
+        else:
+            tcparams[key] = (value, False)
+
+    if one_is_multi:
+        ret = []
+        for incr in range(len_multi):
+            tcp = {}
+            for key, couple in tcparams.items():
+                value, ismulti = couple
+                if ismulti and value != None:
+                    tcp[key] = value[incr]
+                else:
+                    tcp[key] = value
+            ret.append(calculate(name, callback, tcp))
+        return ret
+    else:
+        tcp = {}
+        for key, couple in tcparams.items():
+            tcp[key] = couple[0]
+        return calculate(name, callback, tcp)
+    
+def calculate(name, callback, tcparams):
+    try:
+        # XXX not only creole...
+        from creole import eosfunc
+        return getattr(eosfunc, callback)(**tcparams) 
+    except AttributeError, err:
+        import traceback
+        traceback.print_exc()
+        raise SpecialOwnersError("callback: {0} return error {1} for "
+                       "option: {2}".format(callback, str(err), name))
+
diff --git a/tiramisu/basetype.py b/tiramisu/basetype.py
new file mode 100644 (file)
index 0000000..c501ccb
--- /dev/null
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"base 'interface' types for option types"
+# Copyright (C) 2012 Team tiramisu (see README for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# The original `Config` design model is unproudly borrowed from 
+# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+# Option and OptionDescription modes
+modes = ['normal', 'expert']
+
+class HiddenBaseType(object):
+    hidden = False
+    def hide(self):
+        self.hidden = True
+    def show(self):
+        self.hidden = False
+    def _is_hidden(self):
+        # dangerous method: how an Option can determine its status by itself ? 
+        return self.hidden
+
+class DisabledBaseType(object):
+    disabled = False   
+    def disable(self):
+        self.disabled = True
+    def enable(self):
+        self.disabled = False
+    def _is_disabled(self):
+        return self.disabled
+
+class ModeBaseType(object):
+    mode = 'normal'
+    def get_mode(self):
+        return self.mode
+    def set_mode(self, mode):
+        if mode not in modes:
+            raise TypeError("Unknown mode: {0}".format(mode))
+        self.mode = mode    
+
diff --git a/tiramisu/config.py b/tiramisu/config.py
new file mode 100644 (file)
index 0000000..5a0c748
--- /dev/null
@@ -0,0 +1,540 @@
+# -*- coding: utf-8 -*-
+"pretty small and local configuration management tool"
+# Copyright (C) 2012 Team tiramisu (see README for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# The original `Config` design model is unproudly borrowed from 
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+from copy import copy
+from tiramisu.error import (HiddenOptionError, ConfigError, NotFoundError, 
+                AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, 
+                            SpecialOwnersError, MandatoryError, MethodCallError, 
+                                           DisabledOptionError, ModeOptionError)
+from tiramisu.option import (OptionDescription, Option, SymLinkOption, group_types, 
+                    Multi, apply_requires, modes)
+from tiramisu.autolib import special_owners, special_owner_factory
+# ______________________________________________________________________
+# generic owner. 'default' is the general config owner after init time
+default_owner = 'user'
+# ____________________________________________________________
+class Config(object):
+    _cfgimpl_hidden = True
+    _cfgimpl_disabled = True
+    _cfgimpl_mandatory = True
+    _cfgimpl_frozen = False
+    _cfgimpl_owner = default_owner
+    _cfgimpl_toplevel = None
+    _cfgimpl_mode = 'normal'
+    
+    def __init__(self, descr, parent=None, **overrides):
+        self._cfgimpl_descr = descr
+        self._cfgimpl_value_owners = {}
+        self._cfgimpl_parent = parent
+        # `Config()` indeed takes care of the `Option()`'s values
+        self._cfgimpl_values = {}
+        self._cfgimpl_previous_values = {}
+        # XXX warnings are a great idea, let's make up a better use of it 
+        self._cfgimpl_warnings = []
+        self._cfgimpl_toplevel = self._cfgimpl_get_toplevel()
+        # `freeze()` allows us to carry out this calculation again if necessary 
+        self._cfgimpl_frozen = self._cfgimpl_toplevel._cfgimpl_frozen 
+        self._cfgimpl_build(overrides)
+
+    def _validate_duplicates(self, children):
+        duplicates = []
+        for dup in children:
+            if dup._name not in duplicates:
+                duplicates.append(dup._name)
+            else:
+                raise ConflictConfigError('duplicate option name: ' 
+                    '{0}'.format(dup._name))
+
+    def _cfgimpl_build(self, overrides):
+        self._validate_duplicates(self._cfgimpl_descr._children)
+        for child in self._cfgimpl_descr._children:
+            if isinstance(child, Option):
+                if child.is_multi():
+                    childdef = Multi(copy(child.getdefault()), config=self, 
+                                     child=child)
+                    self._cfgimpl_values[child._name] = childdef
+                    self._cfgimpl_previous_values[child._name] = list(childdef)
+                else:
+                    childdef = child.getdefault()
+                    self._cfgimpl_values[child._name] = childdef
+                    self._cfgimpl_previous_values[child._name] = childdef 
+                if child.getcallback() is not None:
+                    if child._is_hidden():
+                        self._cfgimpl_value_owners[child._name] = 'auto'
+                    else:
+                        self._cfgimpl_value_owners[child._name] = 'fill'
+                else:
+                    if child.is_multi():
+                        self._cfgimpl_value_owners[child._name] = ['default' \
+                            for i in range(len(child.getdefault() ))]
+                    else:
+                        self._cfgimpl_value_owners[child._name] = 'default'
+            elif isinstance(child, OptionDescription):
+                self._validate_duplicates(child._children)
+                self._cfgimpl_values[child._name] = Config(child, parent=self)
+        self.override(overrides)
+
+    def cfgimpl_update(self):
+        "dynamically adds `Option()` or `OptionDescription()`"
+        # Nothing is static. Everything evolve.
+        # FIXME this is an update for new options in the schema only 
+        # see the update_child() method of the descr object 
+        for child in self._cfgimpl_descr._children:
+            if isinstance(child, Option):
+                if child._name not in self._cfgimpl_values:
+                    if child.is_multi():
+                        self._cfgimpl_values[child._name] = Multi(
+                                copy(child.getdefault()), config=self, child=child)
+                        self._cfgimpl_value_owners[child._name] = ['default' \
+                                for i in range(len(child.getdefault() ))]
+                    else:
+                        self._cfgimpl_values[child._name] = copy(child.getdefault())
+                        self._cfgimpl_value_owners[child._name] = 'default'
+            elif isinstance(child, OptionDescription):
+                if child._name not in self._cfgimpl_values:
+                    self._cfgimpl_values[child._name] = Config(child, parent=self)
+
+    def override(self, overrides):
+        for name, value in overrides.iteritems():
+            homeconfig, name = self._cfgimpl_get_home_by_path(name)
+            #  if there are special_owners, impossible to override
+            if homeconfig._cfgimpl_value_owners[name] in special_owners:
+                raise SpecialOwnersError("cannot override option: {0} because "
+                                            "of its special owner".format(name))
+            homeconfig.setoption(name, value, 'default')
+
+    def cfgimpl_set_owner(self, owner):
+        self._cfgimpl_owner = owner
+        for child in self._cfgimpl_descr._children:
+            if isinstance(child, OptionDescription):
+                self._cfgimpl_values[child._name].cfgimpl_set_owner(owner)
+    # ____________________________________________________________
+    def cfgimpl_hide(self):
+        if self._cfgimpl_parent != None:
+            raise MethodCallError("this method root_hide() shall not be"
+                                           "used with non-root Config() object") 
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_hidden = True
+
+    def cfgimpl_show(self):
+        if self._cfgimpl_parent != None:
+            raise MethodCallError("this method root_hide() shall not be"
+                                           "used with non-root Config() object") 
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_hidden = False
+    # ____________________________________________________________
+    def cfgimpl_disable(self):
+        if self._cfgimpl_parent != None:
+            raise MethodCallError("this method root_hide() shall not be"
+                                           "used with non-root Confit() object") 
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_disabled = True
+
+    def cfgimpl_enable(self):
+        if self._cfgimpl_parent != None:
+            raise MethodCallError("this method root_hide() shall not be"
+                                           "used with non-root Confit() object") 
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_disabled = False
+    # ____________________________________________________________
+    def __setattr__(self, name, value):
+        if '.' in name:
+            homeconfig, name = self._cfgimpl_get_home_by_path(name)
+            return setattr(homeconfig, name, value)
+
+        if name.startswith('_cfgimpl_'):
+            self.__dict__[name] = value
+            return
+        if self._cfgimpl_frozen and getattr(self, name) != value:
+            raise TypeError("trying to change a value in a frozen config"
+                                                ": {0} {1}".format(name, value))
+        if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption:
+            self._validate(name, getattr(self._cfgimpl_descr, name))
+        self.setoption(name, value, self._cfgimpl_owner)
+        
+    def _validate(self, name, opt_or_descr):
+        apply_requires(opt_or_descr, self) 
+        if not type(opt_or_descr) == OptionDescription:
+            # hidden options
+            if self._cfgimpl_toplevel._cfgimpl_hidden and \
+                (opt_or_descr._is_hidden() or self._cfgimpl_descr._is_hidden()):
+                raise HiddenOptionError("trying to access to a hidden option:"
+                                                           " {0}".format(name))
+            # disabled options
+            if self._cfgimpl_toplevel._cfgimpl_disabled and \
+            (opt_or_descr._is_disabled() or self._cfgimpl_descr._is_disabled()):
+                raise DisabledOptionError("this option is disabled:"
+                                                            " {0}".format(name))
+            # expert options 
+            # XXX currently doesn't look at the group, is it really necessary ?
+            if self._cfgimpl_toplevel._cfgimpl_mode != 'normal':
+                if opt_or_descr.get_mode() != 'normal':
+                    raise ModeOptionError("this option's mode is not normal:"
+                                                            " {0}".format(name))
+
+    def __getattr__(self, name):
+        # attribute access by passing a path, 
+        # for instance getattr(self, "creole.general.family.adresse_ip_eth0") 
+        if '.' in name:
+            homeconfig, name = self._cfgimpl_get_home_by_path(name)
+            return getattr(homeconfig, name)
+        opt_or_descr = getattr(self._cfgimpl_descr, name)
+        # symlink options 
+        if type(opt_or_descr) == SymLinkOption:
+            return getattr(self, opt_or_descr.path)
+        self._validate(name, opt_or_descr)
+        # special attributes
+        if name.startswith('_cfgimpl_'):
+            # if it were in __dict__ it would have been found already
+            return self.__dict__[name]
+            raise AttributeError("%s object has no attribute %s" %
+                                 (self.__class__, name))
+        if name not in self._cfgimpl_values:
+            raise AttributeError("%s object has no attribute %s" %
+                                 (self.__class__, name))
+        if name in self._cfgimpl_value_owners:
+            owner = self._cfgimpl_value_owners[name]
+            if owner in special_owners:
+                value = self._cfgimpl_values[name]
+                if value != None:
+                    if opt_or_descr.is_multi():
+                        if owner == 'fill' and None not in value:
+                            return value
+                    else:
+                        if owner == 'fill' and value != None:
+                            return value
+                result = special_owner_factory(name, owner, 
+                            value=value,
+                            callback=opt_or_descr.getcallback(),
+                            callback_params=opt_or_descr.getcallback_params(),
+                            config=self._cfgimpl_get_toplevel())
+                # this result **shall not** be a list 
+                # for example, [1, 2, 3, None] -> [1, 2, 3, result]
+                if isinstance(result, list):
+                    raise ConfigError('invalid calculated value returned'
+                        ' for option {0} : shall not be a list'.format(name))
+                if result != None and not opt_or_descr._validate(result):
+                    raise ConfigError('invalid calculated value returned'
+                        ' for option {0}'.format(name))
+                if opt_or_descr.is_multi():
+                    if value == []:
+                        _result = Multi([result], value.config, value.child)
+                    else:
+                        _result = Multi([], value.config, value.child)
+                        for val in value:
+                            if val == None:
+                                val = result
+                            _result.append(val)
+                else:
+                    _result = result
+                return _result
+        # mandatory options
+        if not isinstance(opt_or_descr, OptionDescription):
+            homeconfig = self._cfgimpl_get_toplevel()
+            mandatory = homeconfig._cfgimpl_mandatory
+            if opt_or_descr.is_mandatory() and mandatory:
+                if self._cfgimpl_values[name] == None\
+                  and opt_or_descr.getdefault() == None:
+                    raise MandatoryError("option: {0} is mandatory " 
+                                          "and shall have a value".format(name))
+        return self._cfgimpl_values[name]
+                
+    def __dir__(self):
+        #from_type = dir(type(self))
+        from_dict = list(self.__dict__)
+        extras = list(self._cfgimpl_values)
+        return sorted(set(extras + from_dict))
+
+    def unwrap_from_name(self, name):
+        # didn't have to stoop so low: `self.get()` must be the proper method
+        # **and it is slow**: it recursively searches into the namespaces
+        paths = self.getpaths(allpaths=True)
+        opts = dict([(path, self.unwrap_from_path(path)) for path in paths])
+        all_paths = [p.split(".") for p in self.getpaths()]
+        for pth in all_paths:
+            if name in pth:
+                return opts[".".join(pth)]
+        raise NotFoundError("name: {0} not found".format(name))
+        
+    def unwrap_from_path(self, path):
+        # didn't have to stoop so low, `geattr(self, path)` is much better
+        # **fast**: finds the option directly in the appropriate namespace
+        if '.' in path:
+            homeconfig, path = self._cfgimpl_get_home_by_path(path)
+            return getattr(homeconfig._cfgimpl_descr, path)
+        return getattr(self._cfgimpl_descr, path)
+                                            
+    def __delattr__(self, name):
+        # if you use delattr you are responsible for all bad things happening
+        if name.startswith('_cfgimpl_'):
+            del self.__dict__[name]
+            return
+        self._cfgimpl_value_owners[name] = 'default'
+        opt = getattr(self._cfgimpl_descr, name)
+        if isinstance(opt, OptionDescription):
+            raise AttributeError("can't option subgroup")
+        self._cfgimpl_values[name] = getattr(opt, 'default', None)
+
+    def setoption(self, name, value, who=None):
+        #who is **not necessarily** a owner, because it cannot be a list
+        #FIXME : sortir le setoption pour les multi, ca ne devrait pas être la
+        child = getattr(self._cfgimpl_descr, name)
+        if who == None:
+            if child.is_multi():
+                newowner = [self._cfgimpl_owner for i in range(len(value))] 
+            else:
+                newowner = self._cfgimpl_owner
+        else:
+            if type(child) != SymLinkOption:
+                if child.is_multi():
+                    if type(value) != Multi:
+                        if type(value) == list:
+                            value = Multi(value, self, child)
+                        else:
+                            raise ConfigError("invalid value for option:"
+                                       " {0} that is set to multi".format(name))
+                    newowner = [who for i in range(len(value))]
+                else:
+                    newowner = who 
+        if type(child) != SymLinkOption:
+            if name not in self._cfgimpl_values:
+                raise AttributeError('unknown option %s' % (name,))
+            # special owners, a value with a owner *auto* cannot be changed
+            oldowner = self._cfgimpl_value_owners[child._name]
+            if oldowner == 'auto':
+                if who == 'auto':
+                    raise ConflictConfigError('cannot override value to %s for '
+                                          'option %s' % (value, name))
+            if oldowner == who:
+                oldvalue = getattr(self, name)
+                if oldvalue == value: #or who in ("default",):
+                    return
+            child.setoption(self, value, who)
+            # if the value owner is 'auto', set the option to hidden 
+            if who == 'auto':
+                if not child._is_hidden():
+                    child.hide()
+            if (value is None and who != 'default' and not child.is_multi()):
+                child.setowner(self, 'default')
+                self._cfgimpl_values[name] = copy(child.getdefault())
+            elif (value == [] and who != 'default' and child.is_multi()):
+                child.setowner(self, ['default' for i in range(len(child.getdefault()))])
+                self._cfgimpl_values[name] = Multi(copy(child.getdefault()),
+                            config=self, child=child)
+            else:         
+                child.setowner(self, newowner)
+        else:
+            homeconfig = self._cfgimpl_get_toplevel()
+            child.setoption(homeconfig, value, who)
+
+    def set(self, **kwargs):
+        all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
+        for key, value in kwargs.iteritems():
+            key_p = key.split('.')
+            candidates = [p for p in all_paths if p[-len(key_p):] == key_p]
+            if len(candidates) == 1:
+                name = '.'.join(candidates[0])
+                homeconfig, name = self._cfgimpl_get_home_by_path(name)
+                try:
+                    getattr(homeconfig, name)
+                except MandatoryError:
+                    pass
+                except Exception, e:
+                    raise e # HiddenOptionError or DisabledOptionError
+                homeconfig.setoption(name, value, self._cfgimpl_owner)
+            elif len(candidates) > 1:
+                raise AmbigousOptionError(
+                    'more than one option that ends with %s' % (key, ))
+            else:
+                raise NoMatchingOptionFound(
+                    'there is no option that matches %s' 
+                    ' or the option is hidden or disabled'% (key, ))
+
+    def get(self, name):
+        paths = self.getpaths(allpaths=True)
+        pathsvalues = []
+        for path in paths:
+            pathname = path.split('.')[-1]
+            if pathname == name:
+                try:
+                    value = getattr(self, path)            
+                    return value 
+                except Exception, e:
+                    raise e
+        raise NotFoundError("option {0} not found in config".format(name))                    
+
+    def _cfgimpl_get_home_by_path(self, path):
+        """returns tuple (config, name)"""
+        path = path.split('.')
+        
+        for step in path[:-1]:
+            self = getattr(self, step)
+        return self, path[-1]
+
+    def _cfgimpl_get_toplevel(self):
+        while self._cfgimpl_parent is not None:
+            self = self._cfgimpl_parent
+        return self
+
+    def cfgimpl_previous_value(self, path):
+        home, name = self._cfgimpl_get_home_by_path(path)
+        return home._cfgimpl_previous_values[name]
+    
+    def get_previous_value(self, name):
+        return self._cfgimpl_previous_values[name]
+             
+    def add_warning(self, warning):
+        self._cfgimpl_get_toplevel()._cfgimpl_warnings.append(warning)
+
+    def get_warnings(self):
+        return self._cfgimpl_get_toplevel()._cfgimpl_warnings
+    # ____________________________________________________________
+    # freeze and read-write statuses
+    def cfgimpl_freeze(self):
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_frozen = True
+        self._cfgimpl_frozen = True
+
+    def cfgimpl_unfreeze(self):
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_frozen = False
+        self._cfgimpl_frozen = False
+
+    def is_frozen(self):
+        # it should be the same value as self._cfgimpl_frozen...
+        rootconfig = self._cfgimpl_get_toplevel()
+        return rootconfig.__dict__['_cfgimpl_frozen']
+
+    def cfgimpl_read_only(self):
+        # hung up on freeze, hidden and disabled concepts 
+        self.cfgimpl_freeze()
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_hidden = False
+        rootconfig._cfgimpl_disabled = True
+        rootconfig._cfgimpl_mandatory = True
+
+    def cfgimpl_set_mode(self, mode):
+        # normal or expert mode
+        rootconfig = self._cfgimpl_get_toplevel()
+        if mode not in modes:
+            raise ConfigError("mode {0} not available".format(mode))
+        rootconfig._cfgimpl_mode = mode
+    
+    def cfgimpl_read_write(self):
+        # hung up on freeze, hidden and disabled concepts
+        self.cfgimpl_unfreeze()
+        rootconfig = self._cfgimpl_get_toplevel()
+        rootconfig._cfgimpl_hidden = True
+        rootconfig._cfgimpl_disabled = True
+        rootconfig._cfgimpl_mandatory = False
+    # ____________________________________________________________
+    def getkey(self):
+        return self._cfgimpl_descr.getkey(self)
+
+    def __hash__(self):
+        return hash(self.getkey())
+
+    def __eq__(self, other):
+        return self.getkey() == other.getkey()
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __iter__(self):
+        # iteration only on Options (not OptionDescriptions)
+        for child in self._cfgimpl_descr._children:
+            if isinstance(child, Option):
+                try:
+                    yield child._name, getattr(self, child._name)
+                except:
+                    pass # hidden, disabled option group
+
+    def iter_groups(self, group_type=None):
+        "iteration on OptionDescriptions"
+        if group_type == None:
+            groups = group_types
+        else:
+            if group_type not in group_types:
+                raise TypeError("Unknown group_type: {0}".format(group_type))
+            groups = [group_type]
+        for child in self._cfgimpl_descr._children:
+            if isinstance(child, OptionDescription):
+                    try:
+                        if child.get_group_type() in groups: 
+                            yield child._name, getattr(self, child._name)
+                    except:
+                        pass # hidden, disabled option
+                    
+    def __str__(self, indent=""):
+        lines = []
+        children = [(child._name, child)
+                    for child in self._cfgimpl_descr._children]
+        children.sort()
+        for name, child in children:
+            if self._cfgimpl_value_owners.get(name, None) == 'default':
+                continue
+            value = getattr(self, name)
+            if isinstance(value, Config):
+                substr = value.__str__(indent + "    ")
+            else:
+                substr = "%s    %s = %s" % (indent, name, value)
+            if substr:
+                lines.append(substr)
+        if indent and not lines:
+            return ''   # hide subgroups with all default values
+        lines.insert(0, "%s[%s]" % (indent, self._cfgimpl_descr._name,))
+        return '\n'.join(lines)
+
+    def getpaths(self, include_groups=False, allpaths=False):
+        """returns a list of all paths in self, recursively, taking care of 
+        the context (hidden/disabled)
+        """
+        paths = []
+        for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
+            try: 
+                value = getattr(self, path)
+            except Exception, e:
+                if not allpaths:
+                    pass # hidden or disabled option
+                else:
+                    paths.append(path) # hidden or disabled option added
+            else:
+                paths.append(path)
+        return paths 
+        
+def make_dict(config, flatten=False):
+    paths = config.getpaths()
+    pathsvalues = []
+    for path in paths:
+        if flatten:
+            pathname = path.split('.')[-1]
+        else:
+            pathname = path
+        try:
+            value = getattr(config, path)            
+            pathsvalues.append((pathname, value))      
+        except:
+            pass # this just a hidden or disabled option  
+    options = dict(pathsvalues)
+    return options
+# ____________________________________________________________
+
diff --git a/tiramisu/error.py b/tiramisu/error.py
new file mode 100644 (file)
index 0000000..4588f71
--- /dev/null
@@ -0,0 +1,25 @@
+class AmbigousOptionError(Exception):
+    pass
+class NoMatchingOptionFound(AttributeError):
+    pass
+class ConfigError(Exception):
+    pass
+class ConflictConfigError(ConfigError):
+    pass
+class HiddenOptionError(AttributeError):
+    pass
+class DisabledOptionError(AttributeError):
+    pass 
+class NotFoundError(Exception):
+    pass
+class MethodCallError(Exception):
+    pass 
+class RequiresError(Exception):
+    pass    
+class MandatoryError(Exception):
+    pass 
+class SpecialOwnersError(Exception):
+    pass 
+class ModeOptionError(Exception):
+    pass
+    
diff --git a/tiramisu/option.py b/tiramisu/option.py
new file mode 100644 (file)
index 0000000..e6d8678
--- /dev/null
@@ -0,0 +1,520 @@
+# -*- coding: utf-8 -*-
+"option types and option description for the configuration management"
+# Copyright (C) 2012 Team tiramisu (see README for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# The original `Config` design model is unproudly borrowed from 
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+from tiramisu.autolib import special_owners
+from tiramisu.basetype import HiddenBaseType, DisabledBaseType, ModeBaseType, modes
+from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError, 
+                   SpecialOwnersError, RequiresError)
+available_actions = ['hide', 'show', 'enable', 'disable'] 
+reverse_actions = {'hide': 'show', 'show': 'hide', 
+                   'disable':'enable', 'enable': 'disable'}
+# ____________________________________________________________
+# OptionDescription authorized group_type values
+group_types = ['default', 'family', 'group', 'master']
+# multi types 
+
+class Multi(list):
+    "container that support items for the values of list (multi) options" 
+    def __init__(self, lst, config, child):
+        self.config = config
+        self.child = child
+        super(Multi, self).__init__(lst)
+        
+    def __setitem__(self, key, value):
+        return self.setoption(value, key)
+
+    def append(self, value):
+        self.setoption(value)
+   
+    def setoption(self, value, key=None):
+        owners = self.child.getowner(self.config)
+        # None is replaced by default_multi
+        if value == None:
+            defval = self.child.getdefault()
+            if key is not None and len(defval) > key:
+                value = defval[key]
+            else:
+                value = self.child.default_multi
+            who = 'default'
+        else:
+            who = self.config._cfgimpl_owner
+            if not self.child._validate(value):
+                raise ConfigError("invalid value {0} "
+                    "for option {1}".format(str(value), self.child._name))
+        oldvalue = list(self)
+        oldowner = self.child.getowner(self.config)
+        if key is None:
+            ret = super(Multi, self).append(value)
+            oldvalue.append(None)
+            oldowner.append(who)
+        else:
+            ret = super(Multi, self).__setitem__(key, value)
+            oldowner[key] = who
+        self.config._cfgimpl_previous_values[self.child._name] = oldvalue
+        self.child.setowner(self.config, oldowner)
+        return ret
+        
+    def pop(self, key):
+        oldowner = self.child.getowner(self.config)
+        oldowner.pop(key)
+        self.child.setowner(self.config, oldowner)
+        super(Multi, self).pop(key)
+# ____________________________________________________________
+#
+class Option(HiddenBaseType, DisabledBaseType, ModeBaseType):
+    #reminder: an Option object is **not** a container for the value
+    _frozen = False
+    def __init__(self, name, doc, default=None, default_multi=None, 
+                 requires=None, mandatory=False, multi=False, callback=None, 
+                 callback_params=None, mode='normal'):
+        self._name = name
+        self.doc = doc
+        self._requires = requires
+        self._mandatory = mandatory
+        self.multi = multi
+        if not self.multi and default_multi is not None:
+            raise ConfigError("a default_multi is set whereas multi is False"
+                  " in option: {0}".format(name)) 
+        if default_multi is not None and not self._validate(default_multi):
+            raise ConfigError("invalid default_multi value {0} "
+                "for option {1}".format(str(default_multi), name))
+        self.default_multi = default_multi
+        #if self.multi and default_multi is None:
+            # _cfgimpl_warnings[name] = DefaultMultiWarning   
+        if callback is not None and (default is not None or default_multi is not None):
+            raise ConfigError("defaut values not allowed if option: {0}" 
+                "is calculated".format(name))
+        self.callback = callback
+        if self.callback is None and callback_params is not None:
+            raise ConfigError("params defined for a callback function but"
+            " no callback defined yet for option {0}".format(name)) 
+        self.callback_params = callback_params
+        if mode not in modes:
+            raise ConfigError("mode {0} not available".format(mode))
+        self.mode = mode
+        if self.multi == True:
+            if default == None:
+                default = []
+            if not isinstance(default, list) or not self.validate(default):
+                raise ConfigError("invalid default value {0} "
+                "for option {1} : not list type".format(str(default), name))
+        else:
+            if default != None and not self.validate(default):
+                raise ConfigError("invalid default value {0} " 
+                                         "for option {1}".format(str(default), name))
+        self.default = default
+        
+    def validate(self, value):
+        if self.multi == False:
+            # None allows the reset of the value
+            if value != None:
+                return self._validate(value)
+        else:
+            if not isinstance(value, list): 
+                raise ConfigError("invalid value {0} " 
+                        "for option {1} which must be a list".format(value,
+                        self._name))
+            for val in value:
+                if val != None:
+                    # None allows the reset of the value
+                    if not self._validate(val):
+                        return False
+        return True
+
+    def getdefault(self):
+        return self.default
+
+    def getdoc(self):
+        return self.doc
+
+    def getcallback(self):
+        return self.callback
+
+    def getcallback_params(self):
+        return self.callback_params
+
+    def setowner(self, config, owner):
+        # config *must* be only the **parent** config (not the toplevel config) 
+        # owner is a **real* owner, a list is actually allowable here
+        name = self._name
+        if self._frozen:
+            raise TypeError("trying to change a frozen option's owner: %s" % name)
+        if owner in special_owners:
+            if self.callback == None:
+                raise SpecialOwnersError("no callback specified for" 
+                                                      "option {0}".format(name))
+        if self.is_multi():
+            if not type(owner) == list:
+                raise ConfigError("invalid owner for multi "
+                    "option: {0}".format(self._name))
+        config._cfgimpl_value_owners[name] = owner
+
+    def getowner(self, config):
+        # config *must* be only the **parent** config (not the toplevel config) 
+        return config._cfgimpl_value_owners[self._name]
+
+    def setoption(self, config, value, who):
+        "who is **not necessarily** a owner because it cannot be a list"
+        name = self._name
+        if self._frozen:
+            raise TypeError('trying to change a frozen option object: %s' % name)
+        # we want the possibility to reset everything
+        if who == "default" and value is None:
+            self.default = None
+            return
+        if not self.validate(value):
+            raise ConfigError('invalid value %s for option %s' % (value, name))
+        if who == "default":
+            # changes the default value (and therefore resets the previous value)
+            if self._validate(value):
+                self.default = value
+            else:
+                raise ConfigError("invalid value %s for option %s" % (value, name))
+        apply_requires(self, config)
+        # FIXME put the validation for the multi somewhere else
+#            # it is a multi **and** it has requires
+#            if self.multi == True:
+#                if type(value) != list:
+#                    raise TypeError("value {0} must be a list".format(value))
+#                if self._requires is not None:
+#                    for reqname in self._requires:
+#                        # FIXME : verify that the slaves are all multi
+#                        #option = getattr(config._cfgimpl_descr, reqname)
+#    #                    if not option.multi == True:
+#    #                        raise ConflictConfigError("an option with requires "
+#    #                         "has to be a list type : {0}".format(name)) 
+#                        if len(config._cfgimpl_values[reqname]) != len(value):
+#                            raise ConflictConfigError("an option with requires "
+#                             "has not the same length of the others " 
+#                             "in the group : {0}".format(reqname)) 
+        if type(config._cfgimpl_values[name]) == Multi:
+            config._cfgimpl_previous_values[name] = list(config._cfgimpl_values[name])
+        else:
+            config._cfgimpl_previous_values[name] = config._cfgimpl_values[name] 
+        config._cfgimpl_values[name] = value
+
+    def getkey(self, value):
+        return value
+
+    def freeze(self):
+        self._frozen = True
+        return True
+
+    def unfreeze(self):
+        self._frozen = False
+    # ____________________________________________________________
+    def is_multi(self):
+        return self.multi
+
+    def is_mandatory(self):
+        return self._mandatory
+        
+class ChoiceOption(Option):
+    opt_type = 'string'
+    
+    def __init__(self, name, doc, values, open_values=False, default=None,
+                 requires=None, callback=None, callback_params=None,
+                 multi=False, mandatory=False):
+        self.values = values
+        if open_values not in [True, False]:
+            raise ConfigError('Open_values must be a boolean for '
+                              '{0}'.format(name))
+        self.open_values = open_values
+        super(ChoiceOption, self).__init__(name, doc, default=default,
+                           callback=callback, callback_params=callback_params, 
+                           requires=requires, multi=multi, mandatory=mandatory)
+
+    def setoption(self, config, value, who):
+        name = self._name
+        super(ChoiceOption, self).setoption(config, value, who)
+
+    def _validate(self, value):
+        if not self.open_values:
+            return value is None or value in self.values
+        else:
+            return True
+
+class BoolOption(Option):
+    opt_type = 'bool'
+    
+#    def __init__(self, name, doc, default=None, requires=None,
+#                                  validator=None, multi=False, mandatory=False):
+#        super(BoolOption, self).__init__(name, doc, default=default, 
+#                            requires=requires, multi=multi, mandatory=mandatory)
+        #self._validator = validator
+
+    def _validate(self, value):
+        return isinstance(value, bool)
+
+# FIXME config level validator             
+#    def setoption(self, config, value, who):
+#        name = self._name
+#        if value and self._validator is not None:
+#            toplevel = config._cfgimpl_get_toplevel()
+#            self._validator(toplevel)
+#        super(BoolOption, self).setoption(config, value, who)
+
+class IntOption(Option):
+    opt_type = 'int'
+    
+    def _validate(self, value):
+        try:
+            int(value)
+        except TypeError:
+            return False
+        return True
+                            
+    def setoption(self, config, value, who):
+        try:
+            super(IntOption, self).setoption(config, value, who)
+        except TypeError, e:
+            raise ConfigError(*e.args)
+
+class FloatOption(Option):
+    opt_type = 'float'
+
+    def _validate(self, value):
+        try:
+            float(value)
+        except TypeError:
+            return False
+        return True
+
+    def setoption(self, config, value, who):
+        try:
+            super(FloatOption, self).setoption(config, float(value), who)
+        except TypeError, e:
+            raise ConfigError(*e.args)
+
+class StrOption(Option):
+    opt_type = 'string'
+    
+    def _validate(self, value):
+        return isinstance(value, str)
+                                     
+    def setoption(self, config, value, who):
+        try:
+            super(StrOption, self).setoption(config, value, who)
+        except TypeError, e:
+            raise ConfigError(*e.args)
+
+class SymLinkOption(object): #(HiddenBaseType, DisabledBaseType):
+    opt_type = 'symlink'
+    
+    def __init__(self, name, path):
+        self._name = name
+        self.path = path 
+    
+    def setoption(self, config, value, who):
+        try:
+            setattr(config, self.path, value) # .setoption(self.path, value, who)
+        except TypeError, e:
+            raise ConfigError(*e.args)
+
+class IPOption(Option):
+    opt_type = 'ip'
+    
+    def _validate(self, value):
+        # by now the validation is nothing but a string, use IPy instead
+        return isinstance(value, str)
+                                     
+    def setoption(self, config, value, who):
+        try:
+            super(IPOption, self).setoption(config, value, who)
+        except TypeError, e:
+            raise ConfigError(*e.args)
+
+class NetmaskOption(Option):
+    opt_type = 'netmask'
+    
+    def _validate(self, value):
+        # by now the validation is nothing but a string, use IPy instead
+        return isinstance(value, str)
+                                     
+    def setoption(self, config, value, who):
+        try:
+            super(NetmaskOption, self).setoption(config, value, who)
+        except TypeError, e:
+            raise ConfigError(*e.args)
+
+class ArbitraryOption(Option):
+    def __init__(self, name, doc, default=None, defaultfactory=None, 
+                                   requires=None, multi=False, mandatory=False):
+        super(ArbitraryOption, self).__init__(name, doc, requires=requires,
+                                               multi=multi, mandatory=mandatory)
+        self.defaultfactory = defaultfactory
+        if defaultfactory is not None:
+            assert default is None
+
+    def _validate(self, value):
+        return True
+
+    def getdefault(self):
+        if self.defaultfactory is not None:
+            return self.defaultfactory()
+        return self.default
+
+class OptionDescription(HiddenBaseType, DisabledBaseType, ModeBaseType):
+    group_type = 'default'
+    
+    def __init__(self, name, doc, children, requires=None):
+        self._name = name
+        self.doc = doc
+        self._children = children
+        self._requires = requires
+        self._build()
+    
+    def getdoc(self):
+        return self.doc
+
+    def _build(self):
+        for child in self._children:
+            setattr(self, child._name, child)
+
+    def add_child(self, child):
+        "dynamically adds a configuration option"
+        #Nothing is static. Even the Mona Lisa is falling apart.
+        for ch in self._children:
+            if isinstance(ch, Option):
+                if child._name == ch._name:
+                    raise ConflictConfigError("existing option : {0}".format(
+                                                                   child._name))
+        self._children.append(child)
+        setattr(self, child._name, child)
+    
+    def update_child(self, child):
+        "modification of an existing option"
+        # XXX : corresponds to the `redefine`, is it usefull 
+        pass
+        
+    def getkey(self, config):
+        return tuple([child.getkey(getattr(config, child._name))
+                      for child in self._children])
+
+    def getpaths(self, include_groups=False, currpath=None):
+        """returns a list of all paths in self, recursively
+           currpath should not be provided (helps with recursion)
+        """
+        if currpath is None:
+            currpath = []
+        paths = []
+        for option in self._children:
+            attr = option._name
+            if attr.startswith('_cfgimpl'):
+                continue
+            value = getattr(self, attr)
+            if isinstance(value, OptionDescription):
+                if include_groups:
+                    paths.append('.'.join(currpath + [attr]))
+                currpath.append(attr)
+                paths += value.getpaths(include_groups=include_groups,
+                                        currpath=currpath)
+                currpath.pop()
+            else:
+                paths.append('.'.join(currpath + [attr]))
+        return paths
+    # ____________________________________________________________
+
+    def set_group_type(self, group_type):
+        if group_type in group_types:
+            self.group_type = group_type
+        else:
+            raise ConfigError('not allowed value for group_type : {0}'.format(
+                              group_type))
+    
+    def get_group_type(self):
+        return self.group_type
+    # ____________________________________________________________
+    def hide(self):
+        super(OptionDescription, self).hide()
+        # FIXME : AND THE SUBCHILDREN ? 
+        for child in self._children:
+            if isinstance(child, OptionDescription):
+                child.hide()
+    
+    def show(self):
+        # FIXME : AND THE SUBCHILDREN ?? 
+        super(OptionDescription, self).show()
+        for child in self._children:
+            if isinstance(child, OptionDescription):
+                child.show()
+    # ____________________________________________________________
+    def disable(self):
+        super(OptionDescription, self).disable()
+        # FIXME : AND THE SUBCHILDREN ? 
+        for child in self._children:
+            if isinstance(child, OptionDescription):
+                child.disable()
+    
+    def enable(self):
+        # FIXME : AND THE SUBCHILDREN ? 
+        super(OptionDescription, self).enable()
+        for child in self._children:
+            if isinstance(child, OptionDescription):
+                child.enable()
+# ____________________________________________________________
+def apply_requires(opt, config):
+    if hasattr(opt, '_requires'):
+        if opt._requires is not None:
+            # malformed requirements
+            rootconfig = config._cfgimpl_get_toplevel()
+            for req in opt._requires:
+                if not type(req) == tuple and len(req) in (3, 4):
+                    raise RequiresError("malformed requirements for option:"
+                                                   " {0}".format(opt._name))
+            # all actions **must** be identical
+            actions = [req[2] for req in opt._requires]
+            action = actions[0]
+            for act in actions:
+                if act != action:
+                    raise RequiresError("malformed requirements for option:"
+                                                   " {0}".format(opt._name))
+            # filters the callbacks
+            matches = False
+            for req in opt._requires:
+                if len(req) == 3:
+                    name, expected, action = req
+                    inverted = False
+                if len(req) == 4:
+                    name, expected, action, inverted = req
+                    if inverted == 'inverted':
+                        inverted = True
+                homeconfig, shortname = \
+                                      rootconfig._cfgimpl_get_home_by_path(name)
+                # FIXME: doesn't work with 'auto' or 'fill' yet 
+                # (copy the code from the __getattr__
+                if shortname in homeconfig._cfgimpl_values:
+                    value = homeconfig._cfgimpl_values[shortname]
+                    if (not inverted and value == expected) or \
+                            (inverted and value != expected):
+                        if action not in available_actions:
+                            raise RequiresError("malformed requirements"
+                                           " for option: {0}".format(opt._name))
+                        getattr(opt, action)() #.hide() or show() or...
+                        matches = True
+                else: # option doesn't exist ! should not happen...
+                    raise NotFoundError("required option not found: "
+                                                             "{0}".format(name))
+            # no callback has been triggered, then just reverse the action
+            if not matches:
+                getattr(opt, reverse_actions[action])()
+                
diff --git a/tiramisu/tool.py b/tiramisu/tool.py
new file mode 100644 (file)
index 0000000..229703f
--- /dev/null
@@ -0,0 +1,129 @@
+# Copyright (C) 2012 Team tiramisu (see README for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# The original `Config` design model is unproudly borrowed from 
+# the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+from tiramisu.config import Config
+from tiramisu.option import (OptionDescription, Option, ChoiceOption, BoolOption, 
+                    FloatOption, StrOption, IntOption, IPOption, NetmaskOption, 
+                    ArbitraryOption, group_types, apply_requires)
+
+# ____________________________________________________________
+# reverse factory
+# XXX HAAAAAAAAAAAACK (but possibly a good one)
+def reverse_from_paths(data):
+    "rebuilds a (fake) data structure from an unflatten `make_dict()` result"
+    # ____________________________________________________________
+    _build_map = {
+        bool: BoolOption,
+        int: IntOption,
+        float: FloatOption,
+        str: StrOption,
+    }
+    def option_factory(name, value):
+        "dummy -> Option('dummy')"
+        if isinstance(value, list):
+            return _build_map[type(value[0])](name, '', multi=True, default=value)
+        else:            
+            return _build_map[type(value)](name, '', default=value)
+
+    def build_options(data):
+        "config.gc.dummy ->  Option('dummy')"
+        for key, value in data.items():
+            name = key.split('.')[-1]
+            yield (key, option_factory(name, value))
+    # ____________________________________________________________
+    def parent(pathname):
+        "config.gc.dummy -> config.gc"
+        if "." in pathname:
+            return ".".join(pathname.split('.')[:-1])
+        # no parent except rootconfig, naturally returns None    
+
+    def subgroups(pathname):
+        "config.gc.dummy.bool -> [config.gc, config.gc.dummy]"
+        group = parent(pathname)
+        parents =[]
+        while group is not None:
+            parents.append(group)
+            group = parent(group)
+        return parents 
+
+    def build_option_descriptions(data):
+        all_groups = []
+        for key in data.keys():
+            for group in subgroups(key):
+                # so group is unique in the list 
+                if group not in all_groups:
+                    all_groups.append(group)
+        for group in all_groups:
+            name = group.split('.')[-1]
+            yield (group, OptionDescription(name, '', []))
+    # ____________________________________________________________
+    descr = OptionDescription('tiramisu', 'fake rebuild structure', [])
+    cfg = Config(descr)
+    # add descrs in cfg
+    def compare(a, b):
+        l1 = a.split(".")
+        l2 = b.split(".")
+        if len(l1) < len(l2):
+            return -1
+        elif len(l1) > len(l2):
+            return 1
+        else:
+            return 0
+    grps = list(build_option_descriptions(data))
+    groups = dict(grps)
+    grp_paths = [pathname for pathname, opt_descr in grps]
+    grp_paths.sort(compare)
+    for grp in grp_paths:
+        if not "." in grp:
+            cfg._cfgimpl_descr.add_child(groups[grp])
+            cfg.cfgimpl_update()
+        else:
+            parentdescr = cfg.unwrap_from_path(parent(grp))
+            parentdescr.add_child(groups[grp])
+            getattr(cfg, parent(grp)).cfgimpl_update()
+    # add options in descrs
+    for pathname, opt in build_options(data):
+        current_group_name = parent(pathname)
+        if current_group_name == None:
+            cfg._cfgimpl_descr.add_child(opt)
+            cfg.cfgimpl_update()
+        else:
+            curr_grp = groups[current_group_name]
+            curr_grp.add_child(opt)
+            getattr(cfg, current_group_name).cfgimpl_update()
+
+    return cfg
+# ____________________________________________________________
+# extendable type
+class extend(type):
+    """
+    A magic trick for classes, which lets you add methods or attributes to a
+    class
+    """
+    def extend(cls, extclass):
+        bases = list(extclass.__bases__)
+        bases.append(extclass)
+        for cl in bases:
+            for key, value in cl.__dict__.items():
+                if key == '__module__':
+                    continue
+                setattr(cls, key, value)
+
+# ____________________________________________________________
+