commit d9091cc02ae51e84b8c55de5262c8d1847121fcf
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Sun Jul 26 23:04:55 2009 +0200

    [evol] added aspects modification support, complain about missing fields (added for creation too), and better list present (non-empty) attributes

diff --git a/TODO b/TODO
index 5a583c2..d980284 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,4 @@
 - find a way to specify a parent location for new objects
-- remove/add aspect (removing obsolete fields/adding new MUST fields accordingly)
 - search by combination of objects/aspects/fields values
 - handle fields containing a DN (allowing either giving a DN string or something like <object>:<item>)
 - display familly information (preliminary work done for debug mode done, but object type should be recognized and displayed properly)
diff --git a/bin/shadowwalker b/bin/shadowwalker
index 62d95b8..28ce501 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -335,7 +335,7 @@ class ModCommand < Command
 
     self.short_desc = "Modify attributes of an item"
     self.usages_params = [
-                          "<object> <item> (<field>|<relation>)(=|+=|-=)<value> [(<field>|<relation>)(=|+=|-=)<value>] ..."
+                          "<object> <item> (<field>|<relation>|:aspects)(=|+=|-=)<value> [(<field>|<relation>|:aspects)(=|+=|-=)<value>] ..."
                          ]
   end
 
@@ -366,13 +366,13 @@ class ModCommand < Command
 
     modification_done = false
     args.each do |mod_info|
-      mod_info =~ /^([a-zA-Z]+(?::[a-zA-Z]+)?)(=|\+=|-=)(.*)$/
+      mod_info =~ /^([a-zA-Z]*(?::[a-zA-Z]+)?)(=|\+=|-=)(.*)$/
       key = $1
       op = $2
       val = $3
 
-      if key.nil?
-        STDERR.puts "Syntax error in modification parameters: invalid field name" 
+      if key.nil? or op.nil?
+        STDERR.puts "Syntax error in modification parameters: invalid field name, or missing/unknown operator, or empty value" 
         exit 1
       end
 
@@ -434,14 +434,30 @@ class ModCommand < Command
           exit 4
         end
         val = foreign_item_list.first
+      when ''
+        case field
+        when 'aspects'
+          unless item.class.possible_aspects.include?(val)
+            STDERR.puts "No such aspect '#{val}' for object '#{obj_hdl}'."
+            exit 2
+          end
+        else
+          STDERR.puts "Unknown core field '#{field}'"
+          exit 2
+        end
       else
         STDERR.puts "Unknown type '#{type}' for field '#{field}'."
+        exit 2
       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'
+          STDERR.puts "The equality operator is not possible for aspects."
+          exit 2
+        end
         item.send(field + "=", [val])
       when '+='
         case type
@@ -460,6 +476,11 @@ class ModCommand < Command
           end
 
           item.send(field) << val
+        when ''
+          case field
+          when 'aspects'
+            item.add_aspect(val)
+          end
         end
       when '-='
         case type
@@ -468,6 +489,11 @@ class ModCommand < Command
           item.send(field + "=", new_val)
         when 'rel'
           item.send(field).delete(val)
+        when ''
+          case field
+          when 'aspects'
+            item.remove_aspect(val)
+          end
         end
       else
         STDERR.puts "Syntax error in modification parameters: wrong operator"
@@ -477,6 +503,17 @@ class ModCommand < Command
     end
 
     if modification_done
+      missing_fields = item.missing_attributes
+      unless missing_fields.empty?
+        STDERR.puts "Cannot save the modifications; the following fields are missing:"
+        missing_fields.each do |field|
+          str = Translator.translate_field_name(field)
+          str += " [#{field}]" if $program_options[:handles]
+          puts "  - #{str}"
+        end
+        exit 2
+      end
+
       begin
         item.save!
       rescue ActiveLdap::OperationNotPermitted => e
@@ -569,6 +606,8 @@ class CreateCommand < Command
       key = $1
       val = $2
 
+      val = nil if val == ''
+
       if key.nil?
         STDERR.puts "Syntax error in creation parameters: invalid field name"
         exit 1
@@ -604,6 +643,17 @@ class CreateCommand < Command
       item.send(field + "=", val)
     end
 
+    missing_fields = item.missing_attributes
+    unless missing_fields.empty?
+      STDERR.puts "Cannot save the new item; the following fields are missing:"
+      missing_fields.each do |field|
+        str = Translator.translate_field_name(field)
+        str += " [#{field}]" if $program_options[:handles]
+        puts "  - #{str}"
+      end
+      exit 2
+    end
+
     item.save!
   end
 end
diff --git a/lib/ldap_shadows/object.rb b/lib/ldap_shadows/object.rb
index 28e84bd..28a6f21 100755
--- a/lib/ldap_shadows/object.rb
+++ b/lib/ldap_shadows/object.rb
@@ -44,7 +44,7 @@ module LdapShadows
     end
 
     def load_initial_attribute_list
-      @initial_attribute_list ||= self.attributes.keys
+      @initial_attribute_list ||= self.nonempty_attributes
     end
 
     def name
@@ -188,7 +188,7 @@ module LdapShadows
 
       ignored_attrs = self.mapper.get_global_config[:hidden_attributes] || []
       ignored_attrs += self.class.presentation[:hidden_attributes] || []
-      attr_list = self.attributes.keys - ignored_attrs
+      attr_list = self.nonempty_attributes - ignored_attrs
 
       aspects = self.aspects
       rel_list = self.possible_relations
@@ -288,6 +288,28 @@ module LdapShadows
       list
     end
 
+    def add_aspect(aspect)
+      return unless self.class.possible_aspects.include?(aspect)
+
+      aspect_data = self.mapper.get_aspect(aspect)
+      add_class(*aspect_data[:mapping][:classes])
+    end
+
+    def remove_aspect(aspect)
+      return unless self.class.possible_aspects.include?(aspect)
+
+      aspect_data = self.mapper.get_aspect(aspect)
+      remove_class(*aspect_data[:mapping][:classes])
+    end
+
+    def nonempty_attributes
+      self.attributes.collect{|key, val| (val.nil? or val == []) ? nil : key }.compact
+    end
+
+    def missing_attributes
+      self.must.collect{|attr| attr.name } - self.nonempty_attributes - ['objectClass']
+    end
+
     protected
 
     def fetch_attributes_data(attr_list, expert_attributes)
