commit 304a503d300e61e1fa8b7740c8921df97517c97f
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Sun Jul 26 13:33:09 2009 +0200

    [evol/fix] added 'del' command, added command usage information (using cmdparse subclass was necessary), and fixed a parsing mistake in the 'show' command with field and index

diff --git a/TODO b/TODO
index c941e69..d89ee93 100644
--- a/TODO
+++ b/TODO
@@ -2,4 +2,5 @@
 - create item (aspect-less, with at least MUST fields with a field=val syntax, and multiple occurences of the same field allowed to specify multiple values for multi-valued fields)
 - remove/add aspect (removing obsolete fields/adding new MUST fields accordingly)
 - binary fields: allow upload from file (using field[+-]=path syntax in 'mod' operation when detecting binary field)
+- search by combination of objects/aspects/fields values
 - 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 b55a55d..62f96f0 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -44,6 +44,7 @@ config_str_prv_filelist.each do |file|
 end
 config.recursive_symbolize_keys!
 
+
 $program_options = {}
 
 cmdparser = CmdParse::CommandParser.new(true)
@@ -61,334 +62,435 @@ end
 cmdparser.add_command(CmdParse::HelpCommand.new)
 cmdparser.add_command(CmdParse::VersionCommand.new)
 
-ldapctl = Controller.new
-ldapctl.set_global_config(config[:presentation])
+
+$ldapctl = Controller.new
+$ldapctl.set_global_config(config[:presentation])
 config[:aspects].each_pair do |aspect_name, aspect_data|
-  ldapctl.set_aspect(aspect_name, aspect_data)
+  $ldapctl.set_aspect(aspect_name, aspect_data)
 end
 begin
   ActiveLdap::Base.setup_connection(config[:ldap])
 
   config[:objects].each_pair do |obj_name, obj_data|
-    ldapctl.load_object(obj_name, obj_data)
+    $ldapctl.load_object(obj_name, obj_data)
   end
-  ldapctl.load_relations
+  $ldapctl.load_relations
 rescue ActiveLdap::Error => e
   STDERR.puts "LDAP connection error: " + e.to_s
   exit 3
 end
 
-cmd = CmdParse::Command.new('list', false)
-cmd.short_desc = "list objects"
-cmd.set_execution_block do |args|
-  if args.size < 1
-    STDERR.puts "syntax error: no object name given"
-    exit 1
-  end
 
-  obj_hdl = args.shift.singularize
-  case obj_hdl
-  when ':object'
-    puts "=== List of LDAP objects ==="
-    ldapctl.objects.each do |obj_name|
-      puts "  - #{obj_name}"
+class Command < CmdParse::Command
+  attr_accessor :usages_params
+
+  def usage
+    if @usages_params
+      base_usage_str = super.sub("[ARGS]", "")
+      usages = @usages_params.collect {|params| base_usage_str + params }
+      usages.join("\n")
+    else
+      super
     end
-  when ':aspect'
-    if args.empty?
-      STDERR.puts "syntax error: no sub-object name given"
+  end
+end
+
+class ListCommand < Command
+  def initialize
+    super('list', false)
+
+    self.short_desc = "list items of an object"
+    self.usages_params = [
+                          "<object>",
+                          ":objects",
+                          ":aspects <object>"
+                         ]
+  end
+
+  def execute (args)
+    if args.size < 1
+      STDERR.puts "syntax error: no object name given"
       exit 1
     end
 
-    subobj_hdl = args.shift.singularize
-    obj_klass = ldapctl.find_klass(subobj_hdl)
-    if obj_klass.nil?
-      STDERR.puts "No such object '#{obj_hdl}'."
-      exit 2
-    end
+    obj_hdl = args.shift.singularize
+    case obj_hdl
+    when ':object'
+      puts "=== List of LDAP objects ==="
+      $ldapctl.objects.each do |obj_name|
+        puts "  - #{obj_name}"
+      end
+    when ':aspect'
+      if args.empty?
+        STDERR.puts "syntax error: no sub-object name given"
+        exit 1
+      end
 
-    subobj_human_name = Translator.translate_object_name(subobj_hdl)
-    puts "=== List of LDAP aspects of #{subobj_human_name.pluralize} ==="
-    obj_klass.possible_aspects.each do |subobj_name|
-      puts "  - #{subobj_name}"
-    end
-  else
-    obj_klass = ldapctl.find_klass(obj_hdl)
-    if obj_klass.nil?
-      STDERR.puts "No such object '#{obj_hdl}'."
-      exit 2
-    end
+      subobj_hdl = args.shift.singularize
+      obj_klass = $ldapctl.find_klass(subobj_hdl)
+      if obj_klass.nil?
+        STDERR.puts "No such object '#{obj_hdl}'."
+        exit 2
+      end
 
-    obj_human_name = Translator.translate_object_name(obj_hdl)
-    Display.display_item_list("List of #{obj_human_name.pluralize}", obj_klass.find(:all))
-  end
-end
-cmdparser.add_command(cmd)
-
-cmd = CmdParse::Command.new('show', false)
-cmd.short_desc = "show object information"
-cmd.set_execution_block do |args|
-  if args.size < 1
-    STDERR.puts "syntax error: no object name given"
-    exit 1
-  end
-  if args.size < 2
-    STDERR.puts "syntax error: no item name given"
-    exit 1
-  end
+      subobj_human_name = Translator.translate_object_name(subobj_hdl)
+      puts "=== List of LDAP aspects of #{subobj_human_name.pluralize} ==="
+      obj_klass.possible_aspects.each do |subobj_name|
+        puts "  - #{subobj_name}"
+      end
+    else
+      obj_klass = $ldapctl.find_klass(obj_hdl)
+      if obj_klass.nil?
+        STDERR.puts "No such object '#{obj_hdl}'."
+        exit 2
+      end
 
-  obj_hdl = args.shift
-  obj_klass = ldapctl.find_klass(obj_hdl)
-  if obj_klass.nil?
-    STDERR.puts "No such object '#{obj_hdl}'."
-    exit 2
+      obj_human_name = Translator.translate_object_name(obj_hdl)
+      Display.display_item_list("List of #{obj_human_name.pluralize}", obj_klass.find(:all))
+    end
   end
-
-  item_hdl = args.shift
-  begin
-    item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
-  rescue ActiveLdap::EntryNotFound
-    STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
-    exit 2
+end
+cmdparser.add_command(ListCommand.new)
+
+class ShowCommand < Command
+  def initialize
+    super('show', false)
+
+    self.short_desc = "show item information"
+    self.usages_params = [
+                          "<object> <item>",
+                          "<object> <item> <field>#?",
+                          "<object> <item> <field>[#<index>]"
+                         ]
   end
 
-  if args.empty?
-    $program_options[:skip_binary] = true
-    Display.display_item(item, $program_options)
-  else
-    field_info = args.shift
-    field_name, file_no = field_info.split("#")
+  def execute (args)
+    if args.size < 1
+      STDERR.puts "syntax error: no object name given"
+      exit 1
+    end
+    if args.size < 2
+      STDERR.puts "syntax error: no item name given"
+      exit 1
+    end
 
-    unless item.has_attribute?(field_name)
-      STDERR.puts "No such field '#{field_name}' in object '#{obj_hdl}'."
+    obj_hdl = args.shift
+    obj_klass = $ldapctl.find_klass(obj_hdl)
+    if obj_klass.nil?
+      STDERR.puts "No such object '#{obj_hdl}'."
       exit 2
     end
-    unless item.attribute_present?(field_name)
-      STDERR.puts "Field '#{field_name}' in item '#{obj_hdl}/#{item_hdl}' is not present."
+
+    item_hdl = args.shift
+    begin
+      item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
+    rescue ActiveLdap::EntryNotFound
+      STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
       exit 2
     end
 
-    field_data = item.send(field_name)
-
-    if file_no == "?"
-      result = field_data.size
+    if args.empty?
+      $program_options[:skip_binary] = true
+      Display.display_item(item, $program_options)
     else
-      file_no_i = file_no.to_i
-      file_no = (file_no_i.to_s == file_no) ? file_no_i : nil
-
-      attr_info = ActiveLdap::Base.schema.attribute(field_name)
+      field_info = args.shift
+      field_name, file_no = field_info.split("#")
 
-      field_single = attr_info.single_value?
-      if file_no.nil? and not field_single
-        STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' has multiple values, but you didn't select one."
+      unless item.has_attribute?(field_name)
+        STDERR.puts "No such field '#{field_name}' in object '#{obj_hdl}'."
         exit 2
       end
-      if file_no > 0 and field_single
-        STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' has only a single value"
-        exit 2
-      end
-      if file_no < 0 or file_no >= field_data.size
-        STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' doesn't have such file number '#{file_no}' (select in range [0, #{field_data.size - 1}])"
+      unless item.attribute_present?(field_name)
+        STDERR.puts "Field '#{field_name}' in item '#{obj_hdl}/#{item_hdl}' is not present."
         exit 2
       end
 
-      result = field_data[file_no]
-    end
+      field_data = item.send(field_name)
 
-    if args.empty?
-      puts result
-    else
-      file_name = args.shift
-      if File.exists?(file_name)
-        STDERR.puts "File '#{file_name}' already exists, and i won't overwrite it."
-        exit 2
-      end
+      if file_no == "?"
+        result = field_data.size
+      else
+        file_no_i = file_no.to_i
+        file_no = (file_no_i.to_s == file_no) ? file_no_i : nil
 
-      begin
-        open(file_name, "w") do |fp|
-          fp.write result
+        attr_info = ActiveLdap::Base.schema.attribute(field_name)
+
+        field_single = attr_info.single_value?
+        if file_no.nil? and not field_single
+          STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' has multiple values, but you didn't select one."
+          exit 2
         end
-      rescue
-        STDERR.puts "Cannot save file: " + $!
-        exit 3
+
+        file_no = 0 if file_no.nil?
+        if file_no > 0 and field_single
+          STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' has only a single value"
+          exit 2
+        end
+        if file_no < 0 or file_no >= field_data.size
+          STDERR.puts "Field '#{field_name}' in object '#{obj_hdl}' doesn't have such file number '#{file_no}' (select in range [0, #{field_data.size - 1}])"
+          exit 2
+        end
+
+        result = (field_data.is_a?(Array) ? field_data[file_no] : field_data)
       end
 
-      puts "File saved."
+      if args.empty?
+        puts result
+      else
+        file_name = args.shift
+        if File.exists?(file_name)
+          STDERR.puts "File '#{file_name}' already exists, and i won't overwrite it."
+          exit 2
+        end
+
+        begin
+          open(file_name, "w") do |fp|
+            fp.write result
+          end
+        rescue
+          STDERR.puts "Cannot save file: " + $!
+          exit 3
+        end
+
+        puts "File saved."
+      end
     end
   end
 end
-cmdparser.add_command(cmd)
+cmdparser.add_command(ShowCommand.new)
 
-cmd = CmdParse::Command.new('tree', false)
-cmd.short_desc = "show skeleton objects tree"
-cmd.set_execution_block do |args|
-  puts "Tree:"
+class TreeCommand < Command
+  def initialize
+    super('tree', false)
 
-  base_dn = ActiveLdap::DistinguishedName.parse(LdapObject.base)
+    self.short_desc = "show skeleton items tree"
+    self.usages_params = [
+                          ""
+                          ]
+  end
+
+  def execute (args)
+    puts "Tree:"
+
+    base_dn = ActiveLdap::DistinguishedName.parse(LdapObject.base)
 
-  gconfig = ldapctl.get_global_config()
-  if gconfig.has_key?(:tree_objects)
-    tree = {}
+    gconfig = $ldapctl.get_global_config()
+    if gconfig.has_key?(:tree_objects)
+      tree = {}
 
-    gconfig[:tree_objects].each do |obj_hdl|
-      obj_klass = ldapctl.find_klass(obj_hdl)
-      raise "object '#{obj_hdl}' not defined" unless obj_klass
+      gconfig[:tree_objects].each do |obj_hdl|
+        obj_klass = $ldapctl.find_klass(obj_hdl)
+        raise "object '#{obj_hdl}' not defined" unless obj_klass
 
-      obj_klass.find(:all).each do |obj|
-        ptree = tree
-        pdn = nil
-        (obj.dn_obj - base_dn).to_s.split(',').reverse.each do |pdn|
-          ptree[pdn] = {} unless ptree.has_key?(pdn)
-          ptree = ptree[pdn]
+        obj_klass.find(:all).each do |obj|
+          ptree = tree
+          pdn = nil
+          (obj.dn_obj - base_dn).to_s.split(',').reverse.each do |pdn|
+            ptree[pdn] = {} unless ptree.has_key?(pdn)
+            ptree = ptree[pdn]
+          end
+          ptree = nil
         end
-        ptree = nil
       end
-    end
-    tree = {base_dn => tree}
+      tree = {base_dn => tree}
 
-    Display.display_hash_tree(tree, 0)
+      Display.display_hash_tree(tree, 0)
+    end
   end
 end
-cmdparser.add_command(cmd)
-
-cmd = CmdParse::Command.new('mod', false)
-cmd.short_desc = "Modify attributes of an object"
-cmd.set_execution_block do |args|
-  if args.size < 1
-    STDERR.puts "syntax error: no object name given"
-    exit 1
-  end
-  if args.size < 2
-    STDERR.puts "syntax error: no item name given"
-    exit 1
-  end
+cmdparser.add_command(TreeCommand.new)
 
-  obj_hdl = args.shift
-  obj_klass = ldapctl.find_klass(obj_hdl)
-  if obj_klass.nil?
-    STDERR.puts "No such object '#{obj_hdl}'."
-    exit 2
-  end
+class ModCommand < Command
+  def initialize
+    super('mod', false)
 
-  item_hdl = args.shift
-  begin
-    item = obj_klass.find(item_hdl)
-  rescue ActiveLdap::EntryNotFound
-    STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
-    exit 2
+    self.short_desc = "Modify attributes of an item"
+    self.usages_params = [
+                          "<object> <item> (<field>|<relation>)(=|+=|-=)<value> [(<field>|<relation>)(=|+=|-=)<value>] ..."
+                         ]
   end
 
-  modification_done = false
-  args.each do |mod_info|
-    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" 
+  def execute (args)
+    if args.size < 1
+      STDERR.puts "syntax error: no object name given"
       exit 1
     end
-
-    if key.index(":")
-      type, field = key.split(":")
-    else
-      type = nil
-      field = key
+    if args.size < 2
+      STDERR.puts "syntax error: no item name given"
+      exit 1
     end
 
-    case type
-    when nil
-      unless item.has_attribute?(field)
-        STDERR.puts "No such field '#{field}' in object '#{obj_hdl}'." 
-        exit 2
-      end
+    obj_hdl = args.shift
+    obj_klass = $ldapctl.find_klass(obj_hdl)
+    if obj_klass.nil?
+      STDERR.puts "No such object '#{obj_hdl}'."
+      exit 2
+    end
 
-      attr_info = ActiveLdap::Base.schema.attribute(field)
-      if attr_info.read_only?
-        STDERR.puts "The field '#{field}' cannot be modified (read only), skipping."
-        next
-      end
+    item_hdl = args.shift
+    begin
+      item = obj_klass.find(item_hdl)
+    rescue ActiveLdap::EntryNotFound
+      STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
+      exit 2
+    end
 
-      old_val = item.send(field)
-      old_val = [old_val] unless old_val.is_a? Array
-    when 'rel'
-      unless item.relations.include?(field)
-        STDERR.puts "No such relation '#{field}' for object '#{obj_hdl}'." 
-        exit 2
-      end
+    modification_done = false
+    args.each do |mod_info|
+      mod_info =~ /^([a-zA-Z]+(?::[a-zA-Z]+)?)(=|\+=|-=)(.*)$/
+      key = $1
+      op = $2
+      val = $3
 
-      rel_info = item.info_for_relation(field)
-      if rel_info[:read_only]
-        STDERR.puts "The relation '#{field}' cannot be modified (read only), skipping."
-        next
+      if key.nil?
+        STDERR.puts "Syntax error in modification parameters: invalid field name" 
+        exit 1
       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?
-        STDERR.puts "Foreign item '#{val}' for relation '#{field}' not found."
-        exit 2
+      if key.index(":")
+        type, field = key.split(":")
+      else
+        type = nil
+        field = key
       end
-      if foreign_item_list.size > 1
-        STDERR.puts "Ambiguous item '#{val}' for relation '#{field}' (#{foreign_item_list.size} possible items)"
-        exit 4
-      end
-      val = foreign_item_list.first
-    else
-      STDERR.puts "Unknown type '#{type}' for field '#{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 '='
-      item.send(field + "=", [val])
-    when '+='
       case type
       when nil
-        if attr_info.single_value?
-          STDERR.puts "The field '#{field}' cannot hold more than one value."
-          exit 3
+        unless item.has_attribute?(field)
+          STDERR.puts "No such field '#{field}' in object '#{obj_hdl}'." 
+          exit 2
         end
 
-        new_val = old_val << val
-        item.send(field + "=", new_val)
+        attr_info = ActiveLdap::Base.schema.attribute(field)
+        if attr_info.read_only?
+          STDERR.puts "The field '#{field}' cannot be modified (read only), skipping."
+          next
+        end
+
+        old_val = item.send(field)
+        old_val = [old_val] unless old_val.is_a? Array
       when 'rel'
-        if rel_info[:single_value]
-          STDERR.puts "The relation '#{field}' cannot hold more than one foreign item."
-          exit 3
+        unless item.relations.include?(field)
+          STDERR.puts "No such relation '#{field}' for object '#{obj_hdl}'." 
+          exit 2
+        end
+
+        rel_info = item.info_for_relation(field)
+        if rel_info[:read_only]
+          STDERR.puts "The relation '#{field}' cannot be modified (read only), skipping."
+          next
         end
 
-        item.send(field) << val
+        # 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?
+          STDERR.puts "Foreign item '#{val}' for relation '#{field}' not found."
+          exit 2
+        end
+        if foreign_item_list.size > 1
+          STDERR.puts "Ambiguous item '#{val}' for relation '#{field}' (#{foreign_item_list.size} possible items)"
+          exit 4
+        end
+        val = foreign_item_list.first
+      else
+        STDERR.puts "Unknown type '#{type}' for field '#{field}'."
       end
-    when '-='
-      case type
-      when nil
-        new_val = old_val - [val]
-      when 'rel'
-        item.send(field).delete(val)
+
+      # 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 '='
+        item.send(field + "=", [val])
+      when '+='
+        case type
+        when nil
+          if attr_info.single_value?
+            STDERR.puts "The field '#{field}' cannot hold more than one value."
+            exit 3
+          end
+
+          new_val = old_val << val
+          item.send(field + "=", new_val)
+        when 'rel'
+          if rel_info[:single_value]
+            STDERR.puts "The relation '#{field}' cannot hold more than one foreign item."
+            exit 3
+          end
+
+          item.send(field) << val
+        end
+      when '-='
+        case type
+        when nil
+          new_val = old_val - [val]
+        when 'rel'
+          item.send(field).delete(val)
+        end
+      else
+        STDERR.puts "Syntax error in modification parameters: wrong operator"
+        exit 1
       end
+      modification_done = true
+    end
+
+    if modification_done
+      begin
+        item.save!
+      rescue ActiveLdap::OperationNotPermitted => e
+        STDERR.puts "You don't have enough rights to modify--at least--one of these attributes."
+        exit 3
+      end
+
+      puts "Modification done."
     else
-      STDERR.puts "Syntax error in modification parameters: wrong operator"
-      exit 1
+      puts "Nothing to do."
     end
-    modification_done = true
   end
+end
+cmdparser.add_command(ModCommand.new)
 
-  if modification_done
+class DelCommand < Command
+  def initialize
+    super('del', false)
+
+    self.short_desc = "Delete an item"
+    self.usages_params = [
+                          ""
+                         ]
+  end
+
+  def execute (args)
+    if args.size < 1
+      STDERR.puts "syntax error: no object name given"
+      exit 1
+    end
+    if args.size < 2
+      STDERR.puts "syntax error: no item name given"
+      exit 1
+    end
+
+    obj_hdl = args.shift
+    obj_klass = $ldapctl.find_klass(obj_hdl)
+    if obj_klass.nil?
+      STDERR.puts "No such object '#{obj_hdl}'."
+      exit 2
+    end
+
+    item_hdl = args.shift
     begin
-      item.save!
-    rescue ActiveLdap::OperationNotPermitted => e
-      STDERR.puts "You don't have enough rights to modify--at least--one of these attributes."
-      exit 3
+      item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
+    rescue ActiveLdap::EntryNotFound
+      STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
+      exit 2
     end
 
-    puts "Modification done."
-  else
-    puts "Nothing to do."
+    item.delete
+
+    puts "Deletion done."
   end
 end
-cmdparser.add_command(cmd)
+cmdparser.add_command(DelCommand.new)
+
 
 begin
   cmdparser.parse
