-
Notifications
You must be signed in to change notification settings - Fork 3
/
boolean_term_parser.rb
95 lines (82 loc) · 2.18 KB
/
boolean_term_parser.rb
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
require 'parslet'
module BooleanTermParser
# This query parser adds an optional operator ("+" or "-") to the simple term
# parser. In order to do that, a new "clause" node is added to the parse tree.
class QueryParser < Parslet::Parser
rule(:term) { match('[^\s]').repeat(1).as(:term) }
rule(:operator) { (str('+') | str('-')).as(:operator) }
rule(:clause) { (operator.maybe >> term).as(:clause) }
rule(:space) { match('\s').repeat(1) }
rule(:query) { (clause >> space.maybe).repeat.as(:query) }
root(:query)
end
class QueryTransformer < Parslet::Transform
rule(:clause => subtree(:clause)) do
Clause.new(clause[:operator]&.to_s, clause[:term].to_s)
end
rule(:query => sequence(:clauses)) { Query.new(clauses) }
end
class Operator
def self.symbol(str)
case str
when '+'
:must
when '-'
:must_not
when nil
:should
else
raise "Unknown operator: #{str}"
end
end
end
class Clause
attr_accessor :operator, :term
def initialize(operator, term)
self.operator = Operator.symbol(operator)
self.term = term
end
end
class Query
attr_accessor :should_terms, :must_not_terms, :must_terms
def initialize(clauses)
grouped = clauses.chunk { |c| c.operator }.to_h
self.should_terms = grouped.fetch(:should, []).map(&:term)
self.must_not_terms = grouped.fetch(:must_not, []).map(&:term)
self.must_terms = grouped.fetch(:must, []).map(&:term)
end
def to_elasticsearch
query = {
:query => {
:bool => {
}
}
}
if should_terms.any?
query[:query][:bool][:should] = should_terms.map do |term|
match(term)
end
end
if must_terms.any?
query[:query][:bool][:must] = must_terms.map do |term|
match(term)
end
end
if must_not_terms.any?
query[:query][:bool][:must_not] = must_not_terms.map do |term|
match(term)
end
end
query
end
def match(term)
{
:match => {
:title => {
:query => term
}
}
}
end
end
end