commit 1f9bfec2d26868fa820cc1637af26718a0381c8f
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Fri Oct 16 21:36:58 2009 +0200

    [evol] improve config #13 (reorganized config/schema loading/validating, transform Controler into Shadow, and began reworking things to get a real Shadow view)

diff --git a/.gitignore b/.gitignore
index 567788f..9bb2a8a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,8 @@
 \#*#
 # generated by installer
 .config
-lib/ldap_shadows/config.rb
+lib/ldap_shadows/config_setup.rb
 data/locale
 #
-conf/ldap_shadows/shadows/MilkyPond/shadow_private.conf
+conf/ldap_shadows/global_private.conf
 var
diff --git a/bin/shadowwalker b/bin/shadowwalker
index 467302a..8b10268 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -26,7 +26,6 @@ locale = ENV['LANGUAGE'] || ENV['LC_ALL'] || ENV['LC_MESSAGES'] || ENV['LANG']
 
 require 'ldap_shadows'
 require 'ldap_shadows/display_utils'
-require 'yaml'
 require 'cmdparse2'
 
 include LdapShadows
@@ -68,9 +67,9 @@ class Command < CmdParse::Command
   end
 
   def execute(args)
-    $ldapctl = Controller.new
+    config = LdapShadows::Config.instance
     begin
-      $ldapctl.load_shadow($program_options[:shadow_name])
+      $ldapctl = config.load_shadow($program_options[:shadow_name])
     rescue ActiveLdap::Error => e
       STDERR.puts _("LDAP connection error: %s") % e.to_s
       exit 2
diff --git a/lib/ldap_shadows.rb b/lib/ldap_shadows.rb
index e936242..f4b22b3 100644
--- a/lib/ldap_shadows.rb
+++ b/lib/ldap_shadows.rb
@@ -20,7 +20,6 @@ require 'ldap_shadows/info'
 require 'ldap_shadows/config'
 require 'ldap_shadows/lang_additions'
 require 'ldap_shadows/exceptions'
-require 'ldap_shadows/controller'
 require 'gettext'
 require 'pp'
 
diff --git a/lib/ldap_shadows/aspect.rb b/lib/ldap_shadows/aspect.rb
new file mode 100644
index 0000000..13dafb2
--- /dev/null
+++ b/lib/ldap_shadows/aspect.rb
@@ -0,0 +1,34 @@
+#--
+# LdapShadows, a Medium-level LDAP Access Library and Tool.
+# Copyright (c) 2009 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#++
+
+
+module LdapShadows
+  class LdapAspect
+    def self.hook_before_create
+    end
+
+    def self.hook_before_modify
+    end
+
+    protected
+
+    def self.raise_error(msg)
+      raise ProcessingError, msg
+    end
+  end
+end
diff --git a/lib/ldap_shadows/config.rb b/lib/ldap_shadows/config.rb
new file mode 100644
index 0000000..f3d3cc8
--- /dev/null
+++ b/lib/ldap_shadows/config.rb
@@ -0,0 +1,233 @@
+#--
+# LdapShadows, a Medium-level LDAP Access Library and Tool.
+# Copyright (c) 2009 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#++
+
+require 'ldap_shadows/config_setup'
+require 'singleton'
+require 'kwalify'
+require 'ldap_shadows/shadow'
+
+module LdapShadows
+  class Config
+    include Singleton
+
+    def initialize
+      @global_config = nil
+      @schemas = {}
+      @shadows = {}
+    end
+
+    def load_global_config
+      g_config_file = File.join(CFG_DIR, "global.conf")
+      unless File.exists? g_config_file
+        raise PreProcessingError, _("Global LdapShadows config file is missing")
+      end
+
+      g_default_config = {}
+      g_config = g_default_config.merge(YAML.load_file(g_config_file) || {})
+      g_config_str_prv_filelist = [
+        File.join(ENV['HOME'], ".shadowwalker"),
+        File.join(CFG_DIR, "global_private.conf")
+        ]
+      g_config_str_prv_filelist.each do |file|
+        if File.exists?(file)
+          g_config.merge!(YAML.load_file(file))
+          break
+        end
+      end
+      g_config.recursive_symbolize_keys!
+
+      ActiveLdap::Base.setup_connection(g_config[:ldap])
+
+      @global_config = g_config
+    end
+
+    def global_config
+      load_global_config() if @global_config.nil?
+      @global_config
+    end
+
+    def load_shadow(shadow_name = nil)
+      shadow_name = self.global_config[:default_shadow] if shadow_name.nil?
+      if shadow_name.nil?
+        raise PreProcessingError, _("Could not determine which Shadow to travel through")
+      end
+
+      shadow_config_path = File.join(CFG_DIR, "shadows", shadow_name)
+      unless File.exists? shadow_config_path
+        raise PreProcessingError, _("Configuration directory for Shadow '%s' is missing") % shadow_name
+      end
+
+      config_file = File.join(shadow_config_path, "shadow.conf")
+      unless File.exists? config_file
+        raise PreProcessingError, _("General configuration file for Shadow '%s' is missing") % shadow_name
+      end
+      config = YAML.load_file(config_file)
+      config.recursive_symbolize_keys!
+
+      shadow = Shadow.new(shadow_name)
+      shadow.set_shadow_config(config[:presentation])
+
+      load_config_components(shadow_config_path, 'aspects') do |c_name, c_config|
+        shadow.add_aspect(c_name, c_config)
+      end
+
+      load_config_components(shadow_config_path, 'objects') do |c_name, c_config|
+        shadow.load_object(c_name, c_config)
+      end
+
+      shadow.load_relations
+
+      translation_path = File.join(shadow_config_path, "translations")
+      if File.exists? translation_path
+        # load interface translation
+        I18n.load_path += Dir[File.join(translation_path, "**", "*.yml")]
+      end
+
+      @shadows[shadow_name] = {
+        :config_path => shadow_config_path,
+        :shadow => shadow
+      }
+
+      shadow
+    rescue
+      raise PreProcessingError, _("Could not load shadow configuration: %s") % $!
+    end
+
+    def get_shadow_info(shadow_name)
+      @shadows[shadow_name]
+    end
+
+    def parse_and_validate(def_name, type, def_data_raw)
+      schema = load_schema(type)
+
+      validator_klass = case type
+      when 'object'
+        ObjectValidator
+      when 'aspect'
+        AspectValidator
+      else
+        Kwalify::Validator
+      end
+      validator = validator_klass.new(schema)
+
+      # validate config with schema
+      parser = Kwalify::Yaml::Parser.new(validator)
+      def_data = parser.parse(def_data_raw, def_name)
+      raise_if_validation_errors("#{type.capitalize} '#{def_name}'", parser.errors)
+
+      def_data.recursive_symbolize_keys!
+    end
+
+    protected
+
+    def load_config_components(shadow_config_path, type)
+      c_config_dir = File.join(shadow_config_path, type)
+      c_config_pattern = File.join(c_config_dir, "**", "*.conf")
+
+      Dir.glob(c_config_pattern).each do |f|
+        next if f[0..0] == "."
+
+        c_name = File.basename(f).sub(".conf", "")
+        c_config = File.read(f)
+
+        yield(c_name, c_config)
+      end
+    end
+
+    def load_schema(type)
+      schema = @schemas[type]
+      if schema.nil?
+        schema_file = File.join(DATA_DIR, "schema", type + ".yaml")
+        schema = YAML.load_file(schema_file)
+
+        # validate schema
+        metavalidator = Kwalify::MetaValidator.instance
+        errors = metavalidator.validate(schema)
+        raise_if_validation_errors("'#{type}' schema", errors)
+
+        @schemas[type] = schema
+      end
+      schema
+    rescue
+      raise PreProcessingError, _("Could not load schema: %s") % $!
+    end
+
+    def raise_if_validation_errors(name, errors)
+      if errors and not errors.empty?
+        err_msg = []
+        for e in errors
+          err_msg << "[#{e.path}] #{e.message}"
+        end
+        raise PreProcessingError, _("%s is not valid:\n%s") % [name, err_msg.join("\n")]
+      end
+    end
+
+    class LdapShadowsValidator < Kwalify::Validator
+      ## hook method called by Validator#validate()
+      def validate_hook(value, rule, path, errors)
+        msg_list = []
+
+        validate_hook_in(value, rule, path, msg_list)
+
+        msg_list.each do |msg|
+          errors << Kwalify::ValidationError.new(msg, path)
+        end
+      end
+
+      def validate_hook_relation(value, rule, path, msg_list)
+        extra_params = [:type, :object]
+
+        if value['type'] == 'belongs_to'
+          ne_params = value.keys.collect{|k| k.to_sym } - ActiveLdap::Associations::ClassMethods::VALID_BELONGS_TO_OPTIONS - extra_params
+          unless ne_params.empty?
+            msg_list << _("nonexisting relation mapping parameters (%s)") % ne_params.join(", ")
+          end
+        else
+          ne_params = value.keys.collect{|k| k.to_sym } - ActiveLdap::Associations::ClassMethods::VALID_HAS_MANY_OPTIONS - extra_params
+          unless ne_params.empty?
+            msg_list << _("nonexisting relation mapping parameters (%s)") % ne_params.join(", ")
+          end
+        end
+      end
+    end
+
+    class AspectValidator < LdapShadowsValidator
+      def validate_hook_in(value, rule, path, msg_list)
+        case rule.name
+        when 'RelationMapping'
+          validate_hook_relation(value, rule, path, msg_list)
+        end
+      end
+    end
+
+    class ObjectValidator < LdapShadowsValidator
+      def validate_hook_in(value, rule, path, msg_list)
+        case rule.name
+        when 'Mapping'
+          ne_params = value.keys.collect{|k| k.to_sym } - ActiveLdap::Base::VALID_LDAP_MAPPING_OPTIONS
+          unless ne_params.empty?
+            msg_list << _("nonexisting mapping parameters (%s)") % ne_params.join(", ")
+          end
+
+        when 'RelationMapping'
+          validate_hook_relation(value, rule, path, msg_list)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/ldap_shadows/controller.rb b/lib/ldap_shadows/controller.rb
deleted file mode 100755
index 403748c..0000000
--- a/lib/ldap_shadows/controller.rb
+++ /dev/null
@@ -1,374 +0,0 @@
-#--
-# LdapShadows, a Medium-level LDAP Access Library and Tool.
-# Copyright (c) 2009 Marc Dequènes (Duck) <Duck@DuckCorp.org>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
-#++
-
-
-$KCODE = 'UTF8'
-require 'jcode'
-require 'active_ldap'
-require 'kwalify'
-require 'ldap_shadows/activeldap_fixes'
-require 'ldap_shadows/object'
-
-
-module LdapShadows
-  class Controller
-    attr_reader :shadow_name, :shadow_config_path
-
-    def initialize(mod_container = LdapShadows::Objects)
-      @mod_container = mod_container
-
-      @schema = {}
-
-      clear_shadow
-      LdapShadows::LdapObject.mapper = self
-    end
-
-    def clear_shadow
-      @shadow_name = nil
-      @shadow_config_path = nil
-      @global_config = nil
-      @shadow_config = nil
-      @object_definitions = {}
-      @aspects = {}
-      # TODO: should replace @aspects properly one day
-      @aspects2 = {}
-    end
-
-    def set_shadow_config(shadow_config)
-      @shadow_config = shadow_config
-    end
-
-    def get_shadow_config
-      @shadow_config
-    end
-
-    class LdapShadowsValidator < Kwalify::Validator
-      ## hook method called by Validator#validate()
-      def validate_hook(value, rule, path, errors)
-        msg_list = []
-
-        validate_hook_in(value, rule, path, msg_list)
-
-        msg_list.each do |msg|
-          errors << Kwalify::ValidationError.new(msg, path)
-        end
-      end
-
-      def validate_hook_relation(value, rule, path, msg_list)
-        extra_params = [:type, :object]
-
-        if value['type'] == 'belongs_to'
-          ne_params = value.keys.collect{|k| k.to_sym } - ActiveLdap::Associations::ClassMethods::VALID_BELONGS_TO_OPTIONS - extra_params
-          unless ne_params.empty?
-            msg_list << _("nonexisting relation mapping parameters (%s)") % ne_params.join(", ")
-          end
-        else
-          ne_params = value.keys.collect{|k| k.to_sym } - ActiveLdap::Associations::ClassMethods::VALID_HAS_MANY_OPTIONS - extra_params
-          unless ne_params.empty?
-            msg_list << _("nonexisting relation mapping parameters (%s)") % ne_params.join(", ")
-          end
-        end
-      end
-    end
-
-    class AspectValidator < LdapShadowsValidator
-      def validate_hook_in(value, rule, path, msg_list)
-        case rule.name
-        when 'RelationMapping'
-          validate_hook_relation(value, rule, path, msg_list)
-        end
-      end
-    end
-
-    def set_aspect(aspect_name, aspect_def)
-      schema_name = 'aspect'
-      schema = load_schema(schema_name)
-
-      # validate config with schema
-      validator = AspectValidator.new(schema)
-      parser = Kwalify::Yaml::Parser.new(validator)
-      aspect_def = parser.parse(aspect_def, aspect_name)
-      raise_if_validation_errors("#{schema_name.capitalize} '#{aspect_name}'", parser.errors)
-
-      aspect_name = aspect_name.to_sym
-      aspect_def.recursive_symbolize_keys!
-      @aspects[aspect_name] = aspect_def
-
-      return if @shadow_config_path.nil?
-      filename = File.join(@shadow_config_path, "hooks", "aspects", aspect_name.to_s.downcase + ".rb")
-      if File.exists?(filename)
-        klass_name = "LdapAspect" + aspect_name.to_s.capitalize
-        klass_content = IO.read(filename)
-        begin
-          Aspects.module_eval(<<-EOS)
-          class #{klass_name} < Aspect
-            #{klass_content}
-          end
-          EOS
-        rescue
-          raise PreProcessingError, _("Could not load Aspect plugin '%s'") % aspect_name
-        end
-
-        @aspects2[aspect_name] = Aspects.const_get(klass_name)
-      end
-    end
-
-    def get_aspect(aspect_name)
-      @aspects[aspect_name.to_sym]
-    end
-
-    def get_aspect_klass(aspect_name)
-      @aspects2[aspect_name.to_sym]
-    end
-
-    def self.object_name_to_klass_name(obj_name)
-      "LdapObject" + obj_name.to_s.capitalize
-    end
-
-    class ObjectValidator < LdapShadowsValidator
-      def validate_hook_in(value, rule, path, msg_list)
-        case rule.name
-        when 'Mapping'
-          ne_params = value.keys.collect{|k| k.to_sym } - ActiveLdap::Base::VALID_LDAP_MAPPING_OPTIONS
-          unless ne_params.empty?
-            msg_list << _("nonexisting mapping parameters (%s)") % ne_params.join(", ")
-          end
-
-        when 'RelationMapping'
-          validate_hook_relation(value, rule, path, msg_list)
-        end
-      end
-    end
-
-    def load_object(obj_name, obj_def)
-      schema_name = 'object'
-      schema = load_schema(schema_name)
-
-      # validate config with schema
-      validator = ObjectValidator.new(schema)
-      parser = Kwalify::Yaml::Parser.new(validator)
-      obj_def = parser.parse(obj_def, obj_name)
-      raise_if_validation_errors("#{schema_name.capitalize} '#{obj_name}'", parser.errors)
-
-      obj_name = obj_name.to_sym
-      obj_def.recursive_symbolize_keys!
-
-      klass_name = self.class.object_name_to_klass_name(obj_name)
-
-      # create class
-      @mod_container.module_eval(<<-EOS)
-        class #{klass_name} < LdapShadows::LdapObject; end
-      EOS
-
-      # configure class
-      klass = find_klass(obj_name)
-      klass.handle = obj_name
-      klass.presentation = obj_def[:presentation]
-      klass.mapper = self
-      klass.ldap_mapping obj_def[:mapping]
-
-      # store definition for later relations processing
-      @object_definitions[obj_name] = obj_def
-    end
-
-    def find_klass(obj_name)
-      klass_name = self.class.object_name_to_klass_name(obj_name)
-      return nil unless @mod_container.const_defined?(klass_name)
-      @mod_container.const_get(klass_name)
-    end
-
-    # run it _once_ when all objects are loaded
-    def load_relations
-      @object_definitions.each_pair do |obj_name, obj_def|
-        obj_rel = {}
-        obj_rel.merge!(obj_def[:relations]) if obj_def.include?(:relations)
-        if obj_def[:presentation].has_key?(:allowed_aspects)
-          obj_def[:presentation][:allowed_aspects].each do |aspect|
-            aspect_data = get_aspect(aspect)
-            if aspect_data.nil?
-              raise PreProcessingError, _("Aspect '%s' is missing") % aspect
-            end
-            obj_rel.merge!(aspect_data[:relations]) if aspect_data.has_key?(:relations) and aspect_data[:relations]
-          end
-        end
-        next if obj_rel.empty?
-
-        klass = find_klass(obj_name)
-
-        obj_relations_info = {}
-        obj_rel.each_pair do |field_name, rel|
-          foreign_klass = find_klass(rel[:object])
-          if foreign_klass.nil?
-            raise PreProcessingError, _("Relation '%s' for object '%s' is impossible: foreign object '%s' is missing") % [field_name, obj_name, rel[:object]]
-          end
-          rel[:class_name] = foreign_klass.to_s
-
-          case rel[:type]
-          when 'belongs_to'
-            klass.belongs_to field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_BELONGS_TO_OPTIONS.include?(key) }
-          when 'has_many'
-            klass.has_many field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_HAS_MANY_OPTIONS.include?(key) }
-          else
-            raise "bug in '#{obj_name}' object relations (wrong type)"
-          end
-
-          obj_relations_info[field_name] = {
-            :foreign_klass => foreign_klass,
-            :single_value => ActiveLdap::Base.schema.attribute(rel[:foreign_key]).single_value?,
-            :read_only => rel[:read_only] || false
-          }
-        end
-        klass.relations_info = obj_relations_info
-      end
-    end
-
-    def objects
-      @object_definitions.keys.collect{|key| key.to_s }.sort
-    end
-
-    def load_shadow(shadow_name = nil)
-      clear_shadow
-
-      g_config_file = File.join(LdapShadows::Config::CFG_DIR, "global.conf")
-      unless File.exists? g_config_file
-        raise PreProcessingError, _("Global LdapShadows config file is missing")
-      end
-
-      g_default_config = {}
-      g_config = g_default_config.merge(YAML.load(IO.read(g_config_file)) || {})
-      g_config.recursive_symbolize_keys!
-      @global_config = g_config
-
-      @shadow_name = shadow_name || @global_config[:default_shadow]
-      if @shadow_name.nil?
-        raise PreProcessingError, _("Could not determine which Shadow to travel through")
-      end
-
-      @shadow_config_path = File.join(LdapShadows::Config::CFG_DIR, "shadows", @shadow_name)
-      unless File.exists? @shadow_config_path
-        raise PreProcessingError, _("Configuration directory for Shadow '%s' is missing") % @shadow_name
-      end
-
-      config_file = File.join(@shadow_config_path, "shadow.conf")
-      unless File.exists? config_file
-        raise PreProcessingError, _("General configuration file for Shadow '%s' is missing") % @shadow_name
-      end
-      config = YAML.load(IO.read(config_file))
-      config_str_prv_filelist = [
-        File.join(ENV['HOME'], ".shadowwalker"),
-        File.join(@shadow_config_path, "shadow_private.conf")
-        ]
-      config_str_prv_filelist.each do |file|
-        if File.exists?(file)
-          config_str_prv = IO.read(file)
-          config.merge!(YAML.load(config_str_prv))
-          break
-        end
-      end
-      config.recursive_symbolize_keys!
-
-      $ldapctl.set_shadow_config(config[:presentation])
-
-      load_config_components("aspects") do |c_name, c_config|
-        $ldapctl.set_aspect(c_name, c_config)
-      end
-
-      ActiveLdap::Base.setup_connection(config[:ldap])
-
-      load_config_components("objects") do |c_name, c_config|
-        $ldapctl.load_object(c_name, c_config)
-      end
-
-      $ldapctl.load_relations
-
-      translation_path = File.join($ldapctl.shadow_config_path, "translations")
-      if File.exists? translation_path
-        # load interface translation
-        I18n.load_path += Dir[File.join(translation_path, "**", "*.yml")]
-      end
-    rescue
-      raise PreProcessingError, _("Could not load shadow configuration: %s") % $!
-    end
-
-    protected
-
-    def load_config_components(type)
-      c_config_dir = File.join(@shadow_config_path, type)
-      c_config_pattern = File.join(c_config_dir, "**", "*.conf")
-
-      Dir.glob(c_config_pattern).each do |f|
-        next if f[0..0] == "."
-
-        c_name = File.basename(f).sub(".conf", "")
-        c_config = File.read(f)
-
-        yield(c_name, c_config)
-      end
-    end
-
-    def load_schema(type)
-        schema = @schema[type]
-        if schema.nil?
-          schema_file = File.join(Config::DATA_DIR, "schema", type + ".yaml")
-          schema = YAML.load_file(schema_file)
-
-          # validate schema
-          metavalidator = Kwalify::MetaValidator.instance
-          errors = metavalidator.validate(schema)
-          raise_if_validation_errors("'#{type}' schema", errors)
-
-          @schema[type] = schema
-        end
-        schema
-    rescue
-      raise PreProcessingError, _("Could not load schema: %s") % $!
-    end
-
-    def raise_if_validation_errors(name, errors)
-      if errors and not errors.empty?
-        err_msg = []
-        for e in errors
-          err_msg << "[#{e.path}] #{e.message}"
-        end
-        raise PreProcessingError, _("%s is not valid:\n%s") % [name, err_msg.join("\n")]
-      end
-    end
-  end
-
-  # default location for mapped objects
-  module Objects
-  end
-
-  class Aspect
-    def self.hook_before_create
-    end
-
-    def self.hook_before_modify
-    end
-
-    protected
-
-    def self.raise_error(msg)
-      raise ProcessingError, msg
-    end
-  end
-
-  module Aspects
-  end
-end
-
diff --git a/lib/ldap_shadows/shadow.rb b/lib/ldap_shadows/shadow.rb
new file mode 100755
index 0000000..b9eab8a
--- /dev/null
+++ b/lib/ldap_shadows/shadow.rb
@@ -0,0 +1,176 @@
+#--
+# LdapShadows, a Medium-level LDAP Access Library and Tool.
+# Copyright (c) 2009 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#++
+
+
+$KCODE = 'UTF8'
+require 'jcode'
+require 'active_ldap'
+require 'ldap_shadows/activeldap_fixes'
+require 'ldap_shadows/object'
+require 'ldap_shadows/aspect'
+
+
+module LdapShadows
+  class Shadow
+    attr_reader :name
+
+    def initialize(name)
+      @name = name
+
+      LdapShadows::LdapObject.mapper = self
+
+      @config = Config.instance
+
+      @object_definitions = {}
+      @aspects = {}
+      # TODO: should replace @aspects properly one day
+      @aspects2 = {}
+    end
+
+    def set_shadow_config(shadow_config)
+      @shadow_config = shadow_config
+    end
+
+    def get_shadow_config
+      @shadow_config
+    end
+
+    def add_aspect(aspect_name, aspect_def_raw)
+      aspect_def = @config.parse_and_validate(aspect_name, 'aspect', aspect_def_raw)
+
+      aspect_name = aspect_name.to_sym
+      @aspects[aspect_name] = aspect_def
+
+      return if @shadow_config_path.nil?
+      filename = File.join(@shadow_config_path, "hooks", "aspects", aspect_name.to_s.downcase + ".rb")
+      if File.exists?(filename)
+        klass_name = "LdapAspect" + aspect_name.to_s.capitalize
+        klass_content = File.read(filename)
+        begin
+          Aspects.module_eval(<<-EOS)
+          class #{klass_name} < LdapAspect
+            #{klass_content}
+          end
+          EOS
+        rescue
+          raise PreProcessingError, _("Could not load Aspect plugin '%s'") % aspect_name
+        end
+
+        @aspects2[aspect_name] = Aspects.const_get(klass_name)
+      end
+    end
+
+    def get_aspect(aspect_name)
+      @aspects[aspect_name.to_sym]
+    end
+
+    def get_aspect_klass(aspect_name)
+      @aspects2[aspect_name.to_sym]
+    end
+
+    def self.object_name_to_klass_name(object_name)
+      "LdapObject" + object_name.to_s.capitalize
+    end
+
+    def load_object(object_name, object_def_raw)
+      object_def = @config.parse_and_validate(object_name, 'object', object_def_raw)
+
+      object_name = object_name.to_sym
+      klass_name = self.class.object_name_to_klass_name(object_name)
+
+      # create class
+      Objects.module_eval(<<-EOS)
+        class #{klass_name} < LdapObject; end
+      EOS
+
+      # configure class
+      klass = find_klass(object_name)
+      klass.handle = object_name
+      klass.presentation = object_def[:presentation]
+      klass.mapper = self
+      klass.ldap_mapping object_def[:mapping]
+
+      # store definition for later relations processing
+      @object_definitions[object_name] = object_def
+    end
+
+    def find_klass(object_name)
+      klass_name = self.class.object_name_to_klass_name(object_name)
+      return nil unless Objects.const_defined?(klass_name)
+      Objects.const_get(klass_name)
+    end
+
+    # run it _once_ when all objects are loaded
+    def load_relations
+      @object_definitions.each_pair do |object_name, object_def|
+        object_rel = {}
+        object_rel.merge!(object_def[:relations]) if object_def.include?(:relations)
+        if object_def[:presentation].has_key?(:allowed_aspects)
+          object_def[:presentation][:allowed_aspects].each do |aspect|
+            aspect_data = get_aspect(aspect)
+            if aspect_data.nil?
+              raise PreProcessingError, _("Aspect '%s' is missing") % aspect
+            end
+            object_rel.merge!(aspect_data[:relations]) if aspect_data.has_key?(:relations) and aspect_data[:relations]
+          end
+        end
+        next if object_rel.empty?
+
+        klass = find_klass(object_name)
+
+        object_relations_info = {}
+        object_rel.each_pair do |field_name, rel|
+          foreign_klass = find_klass(rel[:object])
+          if foreign_klass.nil?
+            raise PreProcessingError, _("Relation '%s' for object '%s' is impossible: foreign object '%s' is missing") % [field_name, object_name, rel[:object]]
+          end
+          rel[:class_name] = foreign_klass.to_s
+
+          case rel[:type]
+          when 'belongs_to'
+            klass.belongs_to field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_BELONGS_TO_OPTIONS.include?(key) }
+          when 'has_many'
+            klass.has_many field_name, rel.reject {|key, val| not ActiveLdap::Associations::ClassMethods::VALID_HAS_MANY_OPTIONS.include?(key) }
+          else
+            raise "bug in '#{object_name}' object relations (wrong type)"
+          end
+
+          object_relations_info[field_name] = {
+            :foreign_klass => foreign_klass,
+            :single_value => ActiveLdap::Base.schema.attribute(rel[:foreign_key]).single_value?,
+            :read_only => rel[:read_only] || false
+          }
+        end
+        klass.relations_info = object_relations_info
+      end
+    end
+
+    def objects
+      @object_definitions.keys.collect{|key| key.to_s }.sort
+    end
+  end
+
+  # default location for mapped objects
+  module Objects
+  end
+
+  # default location for mapped aspects
+  module Aspects
+  end
+end
+
diff --git a/post-clean.rb b/post-clean.rb
index 1868d34..1366f68 100644
--- a/post-clean.rb
+++ b/post-clean.rb
@@ -18,5 +18,5 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #++
 
-conffile = "lib/cyborghood/config.rb"
+conffile = "lib/cyborghood/config_setup.rb"
 File.unlink(conffile) if FileTest.exists?(conffile)
diff --git a/post-config.rb b/post-config.rb
index e8a7813..62ea1d0 100644
--- a/post-config.rb
+++ b/post-config.rb
@@ -20,7 +20,7 @@
 
 # General header.
 header = <<HEADER
-# config.rb - Coin-diff setup configuration
+# config_setup.rb - Coin-diff setup configuration
 
 #--
 # LdapShadows, a Medium-level LDAP Access Library and Tool.
@@ -47,7 +47,7 @@ HEADER
 
 # Generate config.rb containing general compile/setup time configuration
 # information (in the CoinDiff::Config module).
-File.open('lib/ldap_shadows/config.rb', 'w') do |file|
+File.open('lib/ldap_shadows/config_setup.rb', 'w') do |file|
 
   file.print header
   file.print <<-CONFIG
