commit 35b342c16e599b0eb59e3fcc2944a923334676b3
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Sat Nov 14 17:48:09 2009 +0100

    [evol] search can use family relationship as criterias

diff --git a/TODO b/TODO
index 7e63209..30bf419 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,5 @@
 - allow global relations to share them among objects ???
 - move (relocate) an item
-- search by family relationship ?
 - handle language subtypes ? (would need the RFC2798 preferredLanguage
   parser out of the CyborgHood project)
 - support X-ORDERED attributetypes ?
diff --git a/bin/shadowwalker b/bin/shadowwalker
index cb9493f..34c91ec 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -61,16 +61,18 @@ cmdparser.add_command(CmdParse::VersionCommand.new)
 
 
 class Command < CmdParse::Command
-  attr_accessor :usages_params
+  attr_accessor :usages_params, :usage_extra_info
 
   def usage
-    if @usages_params
+    usage_str = if @usages_params
       base_usage_str = super.sub("[ARGS]", "")
       usages = @usages_params.collect {|params| base_usage_str + params }
       usages.join("\n")
     else
       super
     end
+    usage_str+= "\n" + @usage_extra_info if @usage_extra_info
+    usage_str
   end
 
   def execute(args)
@@ -407,8 +409,9 @@ class SearchCommand < Command
 
     self.short_desc = "Search items"
     self.usages_params = [
-                          "[:objects=<object>[,<object>]...] [:aspects=<aspect>[,<aspect>]...] [<field>=<value>] ..."
+                          "[:objects=<object>[,<object>]...] [:aspects=<aspect>[,<aspect>]...] [<field>=<value>] [:parents|:siblings|:children|:ancestors|:successors=<item-full-handle>[,<item-full-handle>]] ..."
                          ]
+    self.usage_extra_info = "Criterias are AND-ed, but lists of values for a criteria are OR-ed."
   end
 
   def execute(args)
diff --git a/lib/ldap_shadows/manipulation_helper.rb b/lib/ldap_shadows/manipulation_helper.rb
index cf9dccb..b4c4c77 100644
--- a/lib/ldap_shadows/manipulation_helper.rb
+++ b/lib/ldap_shadows/manipulation_helper.rb
@@ -42,6 +42,8 @@ module LdapShadows
       ldap_search_objects = "(objectClass=*)"
       ldap_search_aspects = ""
       ldap_search_fields = []
+      ldap_search_where = []
+      ldap_search_exclude = []
       str_list.each do |str|
         unless str =~ /^([a-zA-Z]*(?::[a-zA-Z]+)?)(=|~=)(.*)$/
           raise SyntaxError, _("search parameter '%s' is invalid") % str
@@ -68,6 +70,37 @@ module LdapShadows
             ldap_search_objects = ldap_search_string_objects(shadow, field, op, val)
           when 'aspects'
             ldap_search_aspects = ldap_search_string_aspects(shadow, field, op, val)
+          when 'parents'
+            val.split(",").each do |parent_fh|
+              parent = find_item_by_full_handle(shadow, parent_fh)
+              ldap_search_where << [parent.dn.to_s, :one]
+            end
+          when 'siblings'
+            val.split(",").each do |sibling_fh|
+              sibling = find_item_by_full_handle(shadow, sibling_fh)
+              ldap_search_where << [sibling.family_parent_dn.to_s, :one]
+              ldap_search_exclude << sibling.dn.to_s
+            end
+          when 'children'
+            val.split(",").each do |child_fh|
+              child = find_item_by_full_handle(shadow, child_fh)
+              ldap_search_where << [child.family_parent_dn.to_s, :base]
+            end
+          when 'ancestors'
+            val.split(",").each do |parent_fh|
+              parent = find_item_by_full_handle(shadow, parent_fh)
+              ldap_search_where << [parent.dn.to_s, :sub]
+              ldap_search_exclude << parent.dn.to_s
+            end
+          when 'successors'
+            val.split(",").each do |child_fh|
+              child = find_item_by_full_handle(shadow, child_fh)
+              dn = child.dn_obj.dup
+              while dn.to_s != ActiveLdap::Base.base.to_s
+                dn.shift
+                ldap_search_where << [dn.to_s, :base]
+              end
+            end
           else
             raise PreProcessingError, _("Unknown core field '%s'") % field
           end
@@ -77,7 +110,15 @@ module LdapShadows
       end
       ldap_search_string = "(&" + ldap_search_objects + ldap_search_aspects + ldap_search_fields.join + ")"
 
-      ActiveLdap::Base.find(:all, :scope => :sub, :filter => ldap_search_string, :attributes => ["objectClass"])
+      if ldap_search_where.empty?
+        ldap_search_where << [ActiveLdap::Base.base.to_s, :sub]
+      end
+      res = []
+      ldap_search_where.each do |where|
+        res += ActiveLdap::Base.find(:all, :base => where[0], :scope => where[1], :filter => ldap_search_string, :attributes => ["objectClass"])
+      end
+
+      res.uniq.select {|item| not ldap_search_exclude.include?(item.dn.to_s) }
     end
 
     def self.find_raw_item_object(shadow, raw_item)
