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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 000949f105a868a2f6e78e31ff582b1c37e08d8f
4
- data.tar.gz: 91a95ee98d16e7401a891f6e50ddd7a957d05aee
3
+ metadata.gz: de94e9fecb06325ad65d0dfd8830452010ced344
4
+ data.tar.gz: 929ca90c0a5f8950c5763f66e5b1ea25ae748bd0
5
5
  SHA512:
6
- metadata.gz: fae7f6d37acf030b967592ad7c0de6cfe9deb8f0f73686d0652f50752c45e2c34066649143de29c3cb3b5b04ed4d8fbb5f888d8b7a641b2c94da70edc107fbf6
7
- data.tar.gz: dd6608f95993537d2d12a6cd7371b1b34109db4ab72583766847a82578c7c0ee9dd86ca5dea06d867b8df7c2a1549a9887617718f85d6fb767f883d663c03f59
6
+ metadata.gz: 8c57aaced27c17568e3be0b6785e34d5c9a3fd4d3b8cd270ea3506fa0d15e57a0a94cef47c42bbbf5314b377a11aaec28d64aaa44d1cdee891fe8f8c9edae601
7
+ data.tar.gz: a249f23ccc42cb90cc308b79e5f8354f5b798f14697fb3e6a9ee8e26102d4fd9ecc772f46fa87f7e3f29777e9ea30bb4a513a28e2a2cad81624a51d49f5689f3
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Searchr
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/searchr`. To experiment with that code, run `bin/console` for an interactive prompt.
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
 
@@ -1,11 +1,8 @@
1
1
  require 'net/http'
2
2
  require 'json'
3
3
 
4
- require "searchr/version"
5
- require 'searchr/query'
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
@@ -2,13 +2,18 @@ require 'addressable/uri'
2
2
 
3
3
  module Searchr
4
4
  class Query
5
- attr_writer :query, :fields_to_query, :start_row, :num_rows
5
+ attr_writer :query, :fields_to_query
6
6
 
7
7
  def search
8
- result_with http_response
8
+ result_class.new self, http_response
9
9
  end
10
10
 
11
- def result_with
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 ||= 0
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 diagnose result weights
127
+ # true required to retrieve result weights
94
128
  true
95
129
  end
96
130
 
@@ -1,12 +1,15 @@
1
1
  module Searchr
2
2
  class Result
3
- def initialize(http_response)
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
@@ -1,12 +1,12 @@
1
1
  module Searchr
2
2
  class SolrQuery < Query
3
- def result_with(http_response)
4
- SolrResult.new http_response
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,
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Searchr
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ["lib"]
21
21
 
22
22
  spec.add_dependency 'addressable'
23
+ spec.add_dependency 'requirer'
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.10"
25
26
  spec.add_development_dependency "rake", "~> 10.0"
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.0
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-01 00:00:00.000000000 Z
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