searchr 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -3
- data/lib/searchr.rb +2 -5
- data/lib/searchr/explanation_parser.rb +293 -0
- data/lib/searchr/query.rb +43 -9
- data/lib/searchr/result.rb +5 -2
- data/lib/searchr/solr_query.rb +3 -3
- data/lib/searchr/solr_result.rb +62 -0
- data/lib/searchr/version.rb +1 -1
- data/searchr.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de94e9fecb06325ad65d0dfd8830452010ced344
|
4
|
+
data.tar.gz: 929ca90c0a5f8950c5763f66e5b1ea25ae748bd0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c57aaced27c17568e3be0b6785e34d5c9a3fd4d3b8cd270ea3506fa0d15e57a0a94cef47c42bbbf5314b377a11aaec28d64aaa44d1cdee891fe8f8c9edae601
|
7
|
+
data.tar.gz: a249f23ccc42cb90cc308b79e5f8354f5b798f14697fb3e6a9ee8e26102d4fd9ecc772f46fa87f7e3f29777e9ea30bb4a513a28e2a2cad81624a51d49f5689f3
|
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Searchr
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Diagnose search result weights. Currently supports solr. To be used with searchr-rails.
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
data/lib/searchr.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
-
require
|
5
|
-
|
6
|
-
require 'searchr/solr_query'
|
7
|
-
require 'searchr/result'
|
8
|
-
require 'searchr/solr_result'
|
4
|
+
require 'requirer'
|
5
|
+
Requirer.require_dir_tree 'searchr'
|
9
6
|
|
10
7
|
module Searchr
|
11
8
|
# Your code goes here...
|
@@ -0,0 +1,293 @@
|
|
1
|
+
module Searchr
|
2
|
+
Explanation = Struct.new(:str, :nested, :flattened)
|
3
|
+
|
4
|
+
class ExplanationParser
|
5
|
+
attr_reader :lines
|
6
|
+
|
7
|
+
def initialize(lines)
|
8
|
+
@lines = lines.select{ |line| !line.empty? }.collect{ |line| ExplainLine.new line }
|
9
|
+
end
|
10
|
+
|
11
|
+
def explanation
|
12
|
+
Explanation.new top_node.simple_string, top_node.simple_json, top_node.simple_array
|
13
|
+
end
|
14
|
+
|
15
|
+
def top_node
|
16
|
+
@top_node ||= scrunch
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def scrunch
|
22
|
+
@_nodes = Array.new(20)
|
23
|
+
@_nodes[0] = @lines[0].node(nil)
|
24
|
+
@_node_depth = 0
|
25
|
+
@_line_num = 1
|
26
|
+
process_next_line until eof?
|
27
|
+
|
28
|
+
return @_nodes[0]
|
29
|
+
end
|
30
|
+
|
31
|
+
def parent_node
|
32
|
+
@_nodes[line.indent_level - 1]
|
33
|
+
end
|
34
|
+
def line
|
35
|
+
@lines[@_line_num]
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_current_node(node, depth)
|
39
|
+
@_node_depth = depth
|
40
|
+
@_nodes[depth] = node
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_next_line
|
44
|
+
node = line.node parent_node
|
45
|
+
set_current_node node, line.indent_level
|
46
|
+
bump_line
|
47
|
+
consume_subordinate_lines node if node.is_leaf
|
48
|
+
end
|
49
|
+
|
50
|
+
def bump_line
|
51
|
+
@_line_num += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def eof?
|
55
|
+
@_line_num >= @lines.size
|
56
|
+
end
|
57
|
+
|
58
|
+
def consume_subordinate_lines(node)
|
59
|
+
current_line = line
|
60
|
+
bump_line
|
61
|
+
while !eof? and (line.indent_level==0 or current_line.indent_level<line.indent_level)
|
62
|
+
node.add_line "#{' '*[line.indent_level-current_line.indent_level,0].max}#{line.line}"
|
63
|
+
bump_line
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class ExplainLine
|
69
|
+
attr_reader :indent_level, :line
|
70
|
+
|
71
|
+
def self.node(str)
|
72
|
+
self.new(str).node
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(line)
|
76
|
+
num_spaces = (/^\s*/.match line).to_s.size
|
77
|
+
@indent_level = num_spaces / 2
|
78
|
+
@line = line[num_spaces..line.size]
|
79
|
+
end
|
80
|
+
|
81
|
+
def node(parent)
|
82
|
+
line_type.new parent, self
|
83
|
+
end
|
84
|
+
|
85
|
+
def line_type
|
86
|
+
return ProductNode if @line.include? ') product of:'
|
87
|
+
return SumNode if @line.include? ') sum of:'
|
88
|
+
return MaxNode if @line.include? ') max of:'
|
89
|
+
return WeightNode if @line.include? ') weight('
|
90
|
+
return CoordNode if @line.include? '= coord('
|
91
|
+
msg = "Unknown line type: #{@line}"
|
92
|
+
msg.logit
|
93
|
+
raise msg
|
94
|
+
end
|
95
|
+
|
96
|
+
def score
|
97
|
+
@score ||= line.to_f
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module Node
|
102
|
+
attr_reader :parent, :explain_line
|
103
|
+
|
104
|
+
def initialize(parent, explain_line)
|
105
|
+
set_parent parent
|
106
|
+
@explain_line = explain_line
|
107
|
+
end
|
108
|
+
|
109
|
+
def score
|
110
|
+
@explain_line.score
|
111
|
+
end
|
112
|
+
|
113
|
+
def children
|
114
|
+
@children ||= []
|
115
|
+
end
|
116
|
+
|
117
|
+
def is_leaf
|
118
|
+
false
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def set_parent(parent)
|
124
|
+
@parent = parent
|
125
|
+
@parent.children << self if parent
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class ProductNode
|
130
|
+
include Node
|
131
|
+
|
132
|
+
def simple_string
|
133
|
+
case children.size
|
134
|
+
when 0
|
135
|
+
''
|
136
|
+
when 1
|
137
|
+
children[0].simple_string
|
138
|
+
else
|
139
|
+
"#{score}=(" + (children.collect {|child| child.simple_string}.join(" * ")) + ")"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def simple_json
|
144
|
+
case children.size
|
145
|
+
when 0
|
146
|
+
nil
|
147
|
+
when 1
|
148
|
+
children[0].simple_json
|
149
|
+
else
|
150
|
+
[score, '*', children.collect {|child| child.simple_json}.select {|ele| !ele.nil?}]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def simple_array
|
155
|
+
results = children.collect {|child| child.simple_array}
|
156
|
+
case results.size
|
157
|
+
when 0
|
158
|
+
[]
|
159
|
+
when 1
|
160
|
+
results[0]
|
161
|
+
else
|
162
|
+
['(', ['*', score]] + results.flatten(1) + [')']
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class SumNode
|
168
|
+
include Node
|
169
|
+
|
170
|
+
def simple_string
|
171
|
+
case children.size
|
172
|
+
when 0
|
173
|
+
''
|
174
|
+
when 1
|
175
|
+
children[0].simple_string
|
176
|
+
else
|
177
|
+
"#{score}=(" + (children.collect {|child| child.simple_string}.join(" + ")) + ")"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def simple_json
|
183
|
+
case children.size
|
184
|
+
when 0
|
185
|
+
nil
|
186
|
+
when 1
|
187
|
+
children[0].simple_json
|
188
|
+
else
|
189
|
+
[score, '+', children.collect {|child| child.simple_json}.select {|ele| !ele.nil?}]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def simple_array
|
194
|
+
results = children.collect {|child| child.simple_array}
|
195
|
+
case results.size
|
196
|
+
when 0
|
197
|
+
[]
|
198
|
+
when 1
|
199
|
+
results[0]
|
200
|
+
else
|
201
|
+
['(', ['+', score]] + results.flatten(1) + [')']
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class MaxNode
|
207
|
+
include Node
|
208
|
+
|
209
|
+
def simple_string
|
210
|
+
case children.size
|
211
|
+
when 0
|
212
|
+
''
|
213
|
+
when 1
|
214
|
+
children[0].simple_string
|
215
|
+
else
|
216
|
+
"#{score}=max(" + (children.collect {|child| child.simple_string}.join(", ")) + ")"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def simple_json
|
222
|
+
case children.size
|
223
|
+
when 0
|
224
|
+
nil
|
225
|
+
when 1
|
226
|
+
children[0].simple_json
|
227
|
+
else
|
228
|
+
[score, 'max', children.collect {|child| child.simple_json}.select {|ele| !ele.nil?}]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def simple_array
|
233
|
+
results = children.collect {|child| child.simple_array}
|
234
|
+
case results.size
|
235
|
+
when 0
|
236
|
+
[]
|
237
|
+
when 1
|
238
|
+
results[0]
|
239
|
+
else
|
240
|
+
['(', ['max', score]] + results.flatten(1) + [')']
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class WeightNode
|
246
|
+
include Node
|
247
|
+
|
248
|
+
def is_leaf
|
249
|
+
true
|
250
|
+
end
|
251
|
+
|
252
|
+
def weight_name
|
253
|
+
m = /\) weight\(([^)]+)/.match explain_line.line
|
254
|
+
m[1]
|
255
|
+
end
|
256
|
+
|
257
|
+
def add_line(line)
|
258
|
+
subordinate_rows << line
|
259
|
+
end
|
260
|
+
|
261
|
+
def subordinate_rows
|
262
|
+
@subordinate_rows ||= []
|
263
|
+
end
|
264
|
+
|
265
|
+
def simple_string
|
266
|
+
"[#{score}=#{weight_name}]"
|
267
|
+
end
|
268
|
+
|
269
|
+
def simple_json
|
270
|
+
[score, weight_name, subordinate_rows]
|
271
|
+
end
|
272
|
+
|
273
|
+
def simple_array
|
274
|
+
[[weight_name, score, subordinate_rows]]
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
class CoordNode
|
279
|
+
include Node
|
280
|
+
|
281
|
+
def simple_string
|
282
|
+
"[#{score}=coord]"
|
283
|
+
end
|
284
|
+
|
285
|
+
def simple_json
|
286
|
+
[score, 'coord']
|
287
|
+
end
|
288
|
+
|
289
|
+
def simple_array
|
290
|
+
[['coord', score]]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
data/lib/searchr/query.rb
CHANGED
@@ -2,13 +2,18 @@ require 'addressable/uri'
|
|
2
2
|
|
3
3
|
module Searchr
|
4
4
|
class Query
|
5
|
-
attr_writer :query, :fields_to_query
|
5
|
+
attr_writer :query, :fields_to_query
|
6
6
|
|
7
7
|
def search
|
8
|
-
|
8
|
+
result_class.new self, http_response
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
# There are more subclass responsibility methods in the protected section.
|
12
|
+
def result_class
|
13
|
+
subclass_responsibility
|
14
|
+
end
|
15
|
+
|
16
|
+
def query_parameters
|
12
17
|
subclass_responsibility
|
13
18
|
end
|
14
19
|
|
@@ -32,14 +37,43 @@ module Searchr
|
|
32
37
|
@fields_to_query ||= default_fields_to_query
|
33
38
|
end
|
34
39
|
|
40
|
+
def fields_to_return
|
41
|
+
@fields_to_return ||= default_fields_to_return
|
42
|
+
@fields_to_return.split(/\W+/)
|
43
|
+
end
|
44
|
+
|
45
|
+
def fields_to_return=(array_or_string)
|
46
|
+
@fields_to_return = if array_or_string.class==String
|
47
|
+
array_or_string
|
48
|
+
else
|
49
|
+
array_or_string.join(' ')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
35
53
|
def start_row
|
36
|
-
@start_row ||=
|
54
|
+
@start_row ||= 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def start_row=(num)
|
58
|
+
num_as_int = Integer(num)
|
59
|
+
raise "start_row must be a positive integer (#{num})" if num_as_int < 1
|
60
|
+
@start_row = num_as_int
|
37
61
|
end
|
38
62
|
|
39
63
|
def num_rows
|
40
64
|
@num_rows ||= default_num_rows
|
41
65
|
end
|
42
66
|
|
67
|
+
def num_rows=(num)
|
68
|
+
num_as_int = Integer(num)
|
69
|
+
raise "num_rows must be a positive integer (#{num})" if num_as_int < 0
|
70
|
+
@num_rows = num_as_int
|
71
|
+
end
|
72
|
+
|
73
|
+
def end_row
|
74
|
+
start_row + num_rows - 1
|
75
|
+
end
|
76
|
+
|
43
77
|
def indent?
|
44
78
|
@do_indent ||= default_indent?
|
45
79
|
end
|
@@ -52,10 +86,6 @@ module Searchr
|
|
52
86
|
@return_type ||= default_return_type
|
53
87
|
end
|
54
88
|
|
55
|
-
def query_parameters
|
56
|
-
subclass_responsibility
|
57
|
-
end
|
58
|
-
|
59
89
|
def url
|
60
90
|
uri = URI(base_query_url)
|
61
91
|
|
@@ -80,6 +110,10 @@ module Searchr
|
|
80
110
|
subclass_responsibility
|
81
111
|
end
|
82
112
|
|
113
|
+
def default_fields_to_return
|
114
|
+
subclass_responsibility
|
115
|
+
end
|
116
|
+
|
83
117
|
def default_num_rows
|
84
118
|
10
|
85
119
|
end
|
@@ -90,7 +124,7 @@ module Searchr
|
|
90
124
|
end
|
91
125
|
|
92
126
|
def default_debug_query?
|
93
|
-
# true required to
|
127
|
+
# true required to retrieve result weights
|
94
128
|
true
|
95
129
|
end
|
96
130
|
|
data/lib/searchr/result.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
module Searchr
|
2
2
|
class Result
|
3
|
-
|
3
|
+
attr_reader :query
|
4
|
+
|
5
|
+
def initialize(query, http_response)
|
6
|
+
@query = query
|
4
7
|
@http_response = http_response
|
5
8
|
raise 'Http response for search did not succeed' if code!=200
|
6
9
|
end
|
7
10
|
|
8
11
|
def body
|
9
|
-
@body ||= JSON.parse(http_response.body)
|
12
|
+
@body ||= JSON.parse(@http_response.body)
|
10
13
|
end
|
11
14
|
|
12
15
|
def code
|
data/lib/searchr/solr_query.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module Searchr
|
2
2
|
class SolrQuery < Query
|
3
|
-
def
|
4
|
-
SolrResult
|
3
|
+
def result_class
|
4
|
+
SolrResult
|
5
5
|
end
|
6
6
|
|
7
7
|
def query_parameters
|
8
8
|
{
|
9
|
-
start: start_row,
|
9
|
+
start: start_row-1,
|
10
10
|
rows: num_rows,
|
11
11
|
q: query,
|
12
12
|
qf: fields_to_query,
|
data/lib/searchr/solr_result.rb
CHANGED
@@ -1,4 +1,66 @@
|
|
1
1
|
module Searchr
|
2
2
|
class SolrResult < Result
|
3
|
+
def documents
|
4
|
+
@documents ||= body['response']['docs']
|
5
|
+
end
|
6
|
+
|
7
|
+
def num_documents_received
|
8
|
+
documents.size
|
9
|
+
end
|
10
|
+
|
11
|
+
def num_documents_requested
|
12
|
+
query.num_rows
|
13
|
+
end
|
14
|
+
|
15
|
+
def start_row
|
16
|
+
@start_row ||= body['response']['start']
|
17
|
+
end
|
18
|
+
|
19
|
+
def end_row
|
20
|
+
start_row + num_documents_received - 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def num_matches
|
24
|
+
@num_found ||= body['response']['numFound']
|
25
|
+
end
|
26
|
+
|
27
|
+
def status
|
28
|
+
status ||= body['responseHeader']['status']
|
29
|
+
end
|
30
|
+
|
31
|
+
def parameters
|
32
|
+
@parameters ||= body['responseHeader']['params']
|
33
|
+
end
|
34
|
+
|
35
|
+
def explain
|
36
|
+
body['debug']['explain']
|
37
|
+
end
|
38
|
+
|
39
|
+
# calculated fields
|
40
|
+
|
41
|
+
def explanations
|
42
|
+
explanation_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def explanation_hash
|
46
|
+
unless @explanation_hash
|
47
|
+
@explanation_hash = extract_explanations
|
48
|
+
@explanation_hash.each do |key, rows|
|
49
|
+
parser = ExplanationParser.new rows
|
50
|
+
rows[0] = parser.explanation
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@explanation_hash
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def extract_explanations
|
59
|
+
matches = {}
|
60
|
+
explain.each do |key, val|
|
61
|
+
matches[key] = val.split("\n")
|
62
|
+
end
|
63
|
+
matches
|
64
|
+
end
|
3
65
|
end
|
4
66
|
end
|
data/lib/searchr/version.rb
CHANGED
data/searchr.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: searchr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Murphy-Dye
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-08-
|
11
|
+
date: 2015-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: requirer
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -166,6 +180,7 @@ files:
|
|
166
180
|
- bin/console
|
167
181
|
- bin/setup
|
168
182
|
- lib/searchr.rb
|
183
|
+
- lib/searchr/explanation_parser.rb
|
169
184
|
- lib/searchr/query.rb
|
170
185
|
- lib/searchr/result.rb
|
171
186
|
- lib/searchr/solr_query.rb
|