summaryrefslogtreecommitdiff
path: root/fake-service/lib/pixelated_service/contacts_search.rb
blob: f7b24ca32571d7f938562ba15cddbdf87a7eb7f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# Very simple search for contacts. The search string will be something that will be prefix matched
# using a boundary before but not after. If you put in more than one word, those two will be searched
# and ANDed together. You can use double quotes or single quotes to do the obvious thing instead
module PixelatedService
  class ContactsSearch
    def initialize(q)
      @qtree = ContactsSearch.compile(q)
    end

    def restrict(input)
      input.select do |mm|
        @qtree.match?(mm)
      end
    end

    REGEXP_DQUOTED = /"[^"]*"/
    REGEXP_SQUOTED = /'[^']*'/
    REGEXP_OTHER = /[^\s]+/

    class AndMatch
      attr_reader :data
      def initialize(data = [])
        @data = data
      end
      def <<(node)
        @data << node
      end
      def match?(c)
        self.data.all? { |mm| mm.match?(c) }
      end
    end

    class StringMatch
      def initialize(data, quoted=false)
        @data = Regexp.new(Regexp.quote(if quoted
                                          data[1..-2]
                                        else
                                          data
                                        end), Regexp::IGNORECASE)
        @exact_match = /\b#{@data}/
      end

      def match_string?(str)
        Array(str).any? { |ff| @exact_match.match ff }
      end

      def match?(c)
        match_string? ([c.name] + c.addresses.to_a).compact
      end
    end

    def self.compile(q)
      qs = StringScanner.new(q)
      qtree = AndMatch.new
      until qs.eos?
        res =
          if qs.check(REGEXP_DQUOTED)
            StringMatch.new(qs.scan(REGEXP_DQUOTED), true)
          elsif qs.check(REGEXP_SQUOTED)
            StringMatch.new(qs.scan(REGEXP_SQUOTED), true)
          elsif qs.check(REGEXP_OTHER)
            StringMatch.new(qs.scan(REGEXP_OTHER))
          end
        qtree << res
        qs.scan(/\s+/)
      end
      qtree
    end
  end
end