commit 3d5fa735b39fa6cb07d52a7f1cae6152ca45188d
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Sun Aug 2 12:56:43 2009 +0200

    [evol] move all item modification into LdapObject, clean things a bit, and use this code for creation too instead of the partial modification support needed to add the MUST attributetypes, this is more flexible

diff --git a/TODO b/TODO
index dcfbc3e..8ec3c21 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,5 @@
+- select parent at creation wirh :parent ? this could be integrated in
+   common code to be able to relocate an item
 - find a way to restrict parent locations for new objects (regex for <obj>/<item> string ? regex for DN ? OR/AND with both ? ???)
 - search by combination of objects/aspects/fields values
 - aspect dependency support
diff --git a/bin/shadowwalker b/bin/shadowwalker
index e924346..de09b4d 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -378,125 +378,8 @@ class ModCommand < Command
 
     modification_done = false
     args.each do |mod_info|
-      unless mod_info =~ /^([a-zA-Z]*(?::[a-zA-Z]+)?)(=|\+=|-=)(.*)$/
-        raise SyntaxError, _("modification parameter '%s' is invalid") % mod_info
-      end
-      key = $1
-      op = $2
-      val = $3
-
-      if key.index(":")
-        type, field = key.split(":")
-      else
-        type = nil
-        field = key
-      end
-
-      case type
-      when nil
-        unless item.has_field?(field)
-          raise PreProcessingError, _("No such field '%s' in object '%s'") % [field, obj_klass.handle]
-        end
-
-        attr_info = ActiveLdap::Base.schema.attribute(field)
-        if attr_info.read_only?
-          raise PreProcessingError, _("The field '%s' cannot be modified (read only)") % field
-        end
-
-        if attr_info.binary?
-          unless File.exists?(val)
-            raise PreProcessingError, _("The field '%s' contains binary data, you must provide a filename instead of a direct value") % field
-          end
-
-          begin
-            val = File.read(val)
-          rescue
-            raise PreProcessingError, _("The file for the binary field '%s' cannot be read: ") % [field, $!]
-          end
-        end
-        old_val = item.send(field, true)
-      when 'rel'
-        unless item.relations.include?(field)
-          raise PreProcessingError, _("No such relation '%s' for object '%s'") % [field, obj_klass.handle]
-        end
-
-        rel_info = item.info_for_relation(field)
-        if rel_info[:read_only]
-          raise PreProcessingError, _("The relation '%s' cannot be modified (read only)") % field
-        end
-
-        # fetch remote object in relation, which will be the real 'val'
-        foreign_item_list = rel_info[:foreign_klass].find(:all, val)
-        if foreign_item_list.empty?
-          raise PreProcessingError, _("Foreign item '%s' for relation '%s' not found") % [val, field]
-        end
-        if foreign_item_list.size > 1
-          raise WeirdError, _("Ambiguous item '%s' for relation '%s' (%s possible items)") %
-            [val, field, foreign_item_list.size]
-        end
-        old_val = item.send(field)
-        val = foreign_item_list.first
-      when ''
-        case field
-        when 'aspects'
-          unless item.class.possible_aspects.include?(val)
-            raise PreProcessingError, _("No such aspect '%s' for object '%s'") % [val, obj_klass.handle]
-          end
-        else
-          raise PreProcessingError, _("Unknown core field '%s'") % field
-        end
-      else
-        raise PreProcessingError, _("Unknown type '%s' for field '%s'") % [type, field]
-      end
-
-      # if val is nil or the latest value is removed, then the atribute is removed from the object,
-      # or the association's attribute is removed from one side
-      case op
-      when '='
-        if type == '' and field == 'aspects'
-          raise PreProcessingError, _("The equality operator is not possible for aspects")
-        end
-        val = [val] if old_val.is_a? Enumerable
-        item.send(field + "=", val)
-      when '+='
-        case type
-        when nil
-          if attr_info.single_value?
-            raise PreProcessingError, _("The field '%s' cannot hold more than one value") % field
-          end
-          unless old_val.include?(val)
-            new_val = old_val << val
-            item.send(field + "=", new_val)
-          end
-        when 'rel'
-          if rel_info[:single_value]
-            raise PreProcessingError, _("The relation '%s' cannot hold more than one foreign item") % field
-          end
-
-          unless old_val.include?(val)
-            item.send(field) << val
-          end
-        when ''
-          case field
-          when 'aspects'
-            item.add_aspect(val)
-          end
-        end
-      when '-='
-        case type
-        when nil
-          new_val = old_val - [val]
-          item.send(field + "=", new_val)
-        when 'rel'
-          item.send(field).delete(val)
-        when ''
-          case field
-          when 'aspects'
-            item.remove_aspect(val)
-          end
-        end
-      end
-      modification_done = true
+      mod_done = item.item_modify_from_string(mod_info)
+      modification_done ||= mod_done
     end
 
     if modification_done
@@ -558,40 +441,15 @@ class CreateCommand < Command
     item = obj_klass.new(item_hdl)
     item.base = loc_item.dn_obj - obj_klass.base_obj
 
-    args.each do |param|
-      raise SyntaxError, _("creation parameter '%s' is invalid") % param unless param =~ /^([a-zA-Z]+)=(.*)$/
-      key = $1
-      val = $2
 
-      val = nil if val == ''
-
-      field = key
-
-      unless item.has_field?(field)
-        raise PreProcessingError, _("No such field '%s' in object '%s'") % [field, obj_klass.handle]
-      end
-
-      attr_info = ActiveLdap::Base.schema.attribute(field)
-      if attr_info.read_only?
-        raise PreProcessingError, _("The field '%s' cannot be modified (read only)") % field
-      end
-
-      if attr_info.binary?
-        unless File.exists?(val)
-          raise PreProcessingError, _("The field '%s' contains binary data, you must provide a filename instead of a direct value") % field
-        end
-
-        begin
-          val = File.read(val)
-        rescue
-          raise PreProcessingError, _("The file for the binary field '%s' cannot be read: %s") % [field, $!]
-        end
-      end
-
-      item.send(field + "=", val)
+    modification_done = false
+    args.each do |mod_info|
+      mod_done = item.item_modify_from_string(mod_info)
+      modification_done ||= mod_done
     end
 
     item.save!
+    puts "Creation done."
   end
 end
 cmdparser.add_command(CreateCommand.new)
diff --git a/lib/ldap_shadows/object.rb b/lib/ldap_shadows/object.rb
index 0a5f9d0..bf4dae2 100755
--- a/lib/ldap_shadows/object.rb
+++ b/lib/ldap_shadows/object.rb
@@ -244,6 +244,172 @@ module LdapShadows
       [obj_info, obj_aspects]
     end
 
+    def item_modify_from_string(str)
+      unless str =~ /^([a-zA-Z]*(?::[a-zA-Z]+)?)(=|\+=|-=)(.*)$/
+        raise SyntaxError, _("modification parameter '%s' is invalid") % str
+      end
+      key = $1
+      op = $2
+      val = $3
+
+      item_modify(key, op, val)
+    end
+
+    def item_modify(key, op, val)
+      if key.index(":")
+        type, field = key.split(":")
+      else
+        type = nil
+        field = key
+      end
+
+      case type
+      when nil
+        item_modify_field(key, op, val)
+      when 'rel'
+        item_modify_relation(field, op, val)
+      when ''
+        case field
+        when 'aspects'
+          item_modify_aspects(op, val)
+        else
+          raise PreProcessingError, _("Unknown core field '%s'") % field
+        end
+      else
+        raise PreProcessingError, _("Unknown type '%s' for field '%s'") % [type, field]
+      end
+    end
+
+    def item_modify_field(field, op, val)
+      unless self.has_field?(field)
+        raise PreProcessingError, _("No such field '%s' in object '%s'") % [field, self.class.handle]
+      end
+
+      attr_info = ActiveLdap::Base.schema.attribute(field)
+      if attr_info.read_only?
+        raise PreProcessingError, _("The field '%s' cannot be modified (read only)") % field
+      end
+
+      if attr_info.binary?
+        unless File.exists?(val)
+          raise PreProcessingError, _("The field '%s' contains binary data, you must provide a filename instead of a direct value") % field
+        end
+
+        begin
+          val = File.read(val)
+        rescue
+          raise PreProcessingError, _("The file for the binary field '%s' cannot be read: ") % [field, $!]
+        end
+      end
+
+      old_val = self.send(field, true)
+
+      # if val is nil or the latest value is removed, then the attribute is removed from the object,
+      case op
+      when '='
+        return false if val == old_val
+
+        val = [val] if old_val.is_a? Enumerable
+        self.send(field + "=", val)
+
+      when '+='
+        if attr_info.single_value?
+          raise PreProcessingError, _("The field '%s' cannot hold more than one value") % field
+        end
+
+        return false if old_val.include?(val)
+        new_val = old_val << val
+        self.send(field + "=", new_val)
+
+      when '-='
+        return false unless old_val.include?(val)
+
+        new_val = old_val - [val]
+        self.send(field + "=", new_val)
+
+      else
+        raise SyntaxError, _("Unknown operator '%s'") % op
+      end
+
+      true
+    end
+
+    def item_modify_relation(rel, op, val)
+      unless self.relations.include?(rel)
+        raise PreProcessingError, _("No such relation '%s' for object '%s'") % [rel, self.class.handle]
+      end
+
+      rel_info = self.info_for_relation(rel)
+      if rel_info[:read_only]
+        raise PreProcessingError, _("The relation '%s' cannot be modified (read only)") % rel
+      end
+
+      # fetch remote object in relation, which will be the real 'val'
+      foreign_item_list = rel_info[:foreign_klass].find(:all, val)
+      if foreign_item_list.empty?
+        raise PreProcessingError, _("Foreign item '%s' for relation '%s' not found") % [val, rel]
+      end
+      if foreign_item_list.size > 1
+        raise WeirdError, _("Ambiguous item '%s' for relation '%s' (%s possible items)") %
+          [val, rel, foreign_item_list.size]
+      end
+
+      old_val = self.send(rel)
+      val = foreign_item_list.first
+
+      # if val is nil or the latest value is removed, then the association's attribute is removed from one side
+      case op
+      when '='
+        val = [val] if old_val.is_a? Enumerable
+        self.send(rel + "=", val)
+
+      when '+='
+        if rel_info[:single_value]
+          raise PreProcessingError, _("The relation '%s' cannot hold more than one foreign item") % rel
+        end
+
+        return false if old_val.include?(val)
+
+        self.send(rel) << val
+
+      when '-='
+        return false unless old_val.include?(val)
+
+        self.send(rel).delete(val)
+
+      else
+        raise SyntaxError, _("Unknown operator '%s'") % op
+      end
+
+      true
+    end
+
+    def item_modify_aspects(op, aspect)
+      unless self.class.possible_aspects.include?(aspect)
+        raise PreProcessingError, _("No such aspect '%s' for object '%s'") % [aspect, self.class.handle]
+      end
+
+      case op
+      when '='
+        raise PreProcessingError, _("The equality operator is not possible for aspects")
+
+      when '+='
+        return false if self.aspects.include?(aspect)
+
+        self.add_aspect(aspect)
+
+      when '-='
+        return false unless self.aspects.include?(aspect)
+
+        self.remove_aspect(aspect)
+
+      else
+        raise SyntaxError, _("Unknown operator '%s'") % op
+      end
+
+      true
+    end
+
     def family_parent_dn
       pdn = self.dn_obj.dup
       pdn.shift
