commit 3f98466868f60ec29793532960f01c329ab4d412
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Sat Oct 9 20:00:52 2010 +0200

    [evol] implemented vattrs search via post-filters (closes #141)

diff --git a/bin/shadowwalker b/bin/shadowwalker
index 33183cb..8802c74 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -409,7 +409,7 @@ class SearchCommand < Command
 
     self.short_desc = "Search items"
     self.usages_params = [
-                          "[:objects=<object>[,<object>]...] [:aspects=<aspect>[,<aspect>]...] [<field>=<value>] [:parents|:siblings|:children|:ancestors|:successors=<item-full-handle>[,<item-full-handle>]] ..."
+                          "[:objects=<object>[,<object>]...] [:aspects=<aspect>[,<aspect>]...] [[filter:]<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
@@ -422,8 +422,8 @@ class SearchCommand < Command
     res = LdapShadows::Manipulation.items_find_from_strings(@shadow, args)
     unless res.empty?
       display_lines = []
-      res.each do |raw_item|
-        display_lines << LdapShadows::Manipulation.raw_item_info(@shadow, raw_item)[:name]
+      res.each do |item_info|
+        display_lines << item_info[:name]
       end
       puts display_lines.join("\n")
     end
diff --git a/lib/ldap_shadows/manipulation_helper.rb b/lib/ldap_shadows/manipulation_helper.rb
index 938b38e..9ba716f 100644
--- a/lib/ldap_shadows/manipulation_helper.rb
+++ b/lib/ldap_shadows/manipulation_helper.rb
@@ -41,12 +41,13 @@ module LdapShadows
       modification_done
     end
 
-    def self.items_find_from_strings(shadow, str_list)
+    def self.items_find_from_strings(shadow, str_list, full_search = false)
       ldap_search_objects = "(objectClass=*)"
       ldap_search_aspects = ""
       ldap_search_fields = Set.new
       ldap_search_where =Set.new
       ldap_search_exclude =Set.new
+      ldap_search_fields_post = Set.new
       str_list.each do |str|
         unless str =~ /^(#{TEXTUI_KEY_PATTERN})(=|~=)(.*)$/
           raise SyntaxError, _("search parameter '%s' is invalid") % str
@@ -65,6 +66,19 @@ module LdapShadows
         case type
         when nil
           ldap_search_fields << ldap_search_string_field(field, op, val)
+        when 'filter'
+          unless ["=", "~="].include? op
+            raise SyntaxError, _("Unknown operator '%s'") % op
+          end
+          if op == "~="
+            begin
+              new_val = Regexp.new(val)
+            rescue RegexpError
+              raise SyntaxError, _("Broken regexp '%s'") % val
+            end
+            val = new_val
+          end
+          ldap_search_fields_post << [field, op, val]
         when 'rel'
           raise PreProcessingError, _("Searching relations is not implemented yet")
         when ''
@@ -113,15 +127,28 @@ module LdapShadows
       end
       ldap_search_string = "(&" + ldap_search_objects + ldap_search_aspects + ldap_search_fields.to_a.join + ")"
 
+      # default search location
       if ldap_search_where.empty?
         ldap_search_where << [ActiveLdap::Base.base, :sub]
       end
+
+      # all attributes needed for post-search (or if full search requested)
+      search_attributes = (full_search or not ldap_search_fields_post.empty?) ? nil : ["objectClass"]
+
+      # search
       res = Set.new
       ldap_search_where.each do |where|
-        res += ActiveLdap::Base.find(:all, :base => where[0], :scope => where[1], :filter => ldap_search_string, :attributes => ["objectClass"])
+        res += ActiveLdap::Base.find(:all, :base => where[0], :scope => where[1], :filter => ldap_search_string, :attributes => search_attributes)
       end
 
-      return res.select {|item| not ldap_search_exclude.include?(item.dn) }
+      # remove excluded entries
+      res.select {|item| not ldap_search_exclude.include?(item.dn) }
+
+      # process result
+      res.collect! {|item| raw_item_info(shadow, item) }
+
+      # post search filters
+      ldap_filter_results(res, ldap_search_fields_post)
     end
 
     def self.find_raw_item_object(shadow, raw_item)
@@ -149,7 +176,7 @@ module LdapShadows
         obj_hdl = self.find_raw_item_object(shadow, raw_item)
         if obj_hdl
           obj_klass = shadow.get_object(obj_hdl)
-          item = obj_klass.new(raw_item.dn)
+          item = obj_klass.find(raw_item.dn)
 
           name = item.full_handle
           name = "root|" + name if is_root
@@ -222,7 +249,7 @@ module LdapShadows
         "(#{field}=#{esc_val})"
 
       when "~="
-        raise PreProcessingError, _("Searching with regex is not implemented yet")
+        raise PreProcessingError, _("Searching with regex is only possible for post-filters")
 
       else
         raise SyntaxError, _("Unknown operator '%s'") % op
@@ -271,5 +298,42 @@ module LdapShadows
       end
       "(&" + ldap_search_parts.join + ")"
     end
+
+    def self.ldap_filter_results(results, ldap_search_fields_post)
+      results.collect do |item_info|
+        recognized_item = item_info[:item]
+
+        if recognized_item.nil?
+          nil
+        else
+          found = true
+          ldap_search_fields_post.each do |search_data|
+            field, op, val = search_data
+            found_val = false
+
+            if recognized_item.has_field?(field)
+              item_values = recognized_item.send(field.to_sym, true)
+
+              item_values.each do |item_val|
+                found_val = case op
+                when "="
+                  item_val == val
+                when "~="
+                  item_val =~ val
+                end
+                break if found_val
+              end
+            end
+
+            unless found_val
+              found = false
+              break
+            end
+          end
+
+          (found) ? item_info : nil
+        end
+      end.compact
+    end
   end
 end
