elasticsearch-explain-response 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/elasticsearch-explain-response.gemspec +1 -1
- data/lib/elasticsearch/api/response/explain_parser.rb +108 -108
- data/lib/elasticsearch/api/response/explain_renderer.rb +6 -97
- data/lib/elasticsearch/api/response/explain_response.rb +20 -10
- data/lib/elasticsearch/api/response/explain_trimmer.rb +76 -0
- data/lib/elasticsearch/api/response/helpers/color_helper.rb +37 -0
- data/lib/elasticsearch/api/response/helpers/string_helper.rb +23 -0
- data/lib/elasticsearch/api/response/renderers/base_renderer.rb +69 -0
- data/lib/elasticsearch/api/response/renderers/hash_renderer.rb +13 -15
- data/lib/elasticsearch/api/response/renderers/inline_renderer.rb +36 -0
- data/lib/elasticsearch/api/response/renderers/standard_renderer.rb +41 -0
- data/spec/elasticsearch/api/response/explain_response_spec.rb +6 -9
- data/spec/fixtures/response2.yml +1 -1
- metadata +7 -3
- data/lib/elasticsearch/api/response/color_helper.rb +0 -35
- data/lib/elasticsearch/api/response/string_helper.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 781deba8c0a09a57fbf21df20918be1f577bfb7c
|
4
|
+
data.tar.gz: 808cd8bd921f1a514cafd1a47457a0575bce6a74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5af238a2068fa0aa82b168b830726674c44f0447c406220907c785f98752c23b11e68dfd6e4032859cf4bcac0683157dcdd2a8610db79f2c6d0764dd75898c8e
|
7
|
+
data.tar.gz: df4a958713b444e87804f7a7d40a1bb09814e00f562d4a0f14f948bcd0bfe76fc432aa3b2e42a7263f64eb5ff50e5687a0235dcf7071a956c3daf2b628968be2
|
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "elasticsearch-explain-response"
|
7
|
-
spec.version = "0.
|
7
|
+
spec.version = "0.2.0"
|
8
8
|
spec.authors = ["Tomoya Hirano"]
|
9
9
|
spec.email = ["hiranotomoya@gmail.com"]
|
10
10
|
spec.summary = %q{Parser for Elasticserach Explain response}
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require "elasticsearch/api/response/string_helper"
|
1
|
+
require "elasticsearch/api/response/helpers/string_helper"
|
2
2
|
|
3
3
|
module Elasticsearch
|
4
4
|
module API
|
5
5
|
module Response
|
6
6
|
class ExplainParser
|
7
|
-
include StringHelper
|
7
|
+
include Helpers::StringHelper
|
8
8
|
|
9
9
|
def parse(explain_tree)
|
10
10
|
root = create_node(explain_tree, level: 0)
|
@@ -14,116 +14,116 @@ module Elasticsearch
|
|
14
14
|
|
15
15
|
private
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
def parse_details(node)
|
27
|
-
node.details.each do |detail|
|
28
|
-
child = create_node(detail, level: node.level.succ)
|
29
|
-
node.children << child
|
30
|
-
parse_details(child)
|
17
|
+
def create_node(detail, level:)
|
18
|
+
ExplainNode.new(
|
19
|
+
score: detail["value"] || 0.0,
|
20
|
+
description: parse_description(detail["description"]),
|
21
|
+
details: detail["details"] || [],
|
22
|
+
level: level
|
23
|
+
)
|
31
24
|
end
|
32
|
-
end
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
field = $1
|
41
|
-
value = $2
|
42
|
-
when /\Aidf\(docFreq\=(\d+)\, maxDocs\=(\d+)\)\z/
|
43
|
-
type = "idf"
|
44
|
-
operation = "idf(#{$1}/#{$2})"
|
45
|
-
when /\Atf\(freq\=([\d.]+)\)\, with freq of\:\z/
|
46
|
-
type = "tf"
|
47
|
-
operation = "tf(#{$1})"
|
48
|
-
when /\Ascore\(doc\=\d+\,freq=[\d\.]+\)\, product of\:\z/
|
49
|
-
type = "score"
|
50
|
-
operation = "score"
|
51
|
-
operator = "x"
|
52
|
-
when /\Amatch filter\: (?:cache\()?(?:(?<op>[\w]+)\()*(?<c>.+)\)*\z/
|
53
|
-
type = "match"
|
54
|
-
operation = "match"
|
55
|
-
operation += ".#{$~[:op]}" if $~[:op] && !%w[QueryWrapperFilter].include?($~[:op])
|
56
|
-
content = $~[:c]
|
57
|
-
content = content[0..-2] if content.end_with?(')')
|
58
|
-
hash = tokenize_contents(content)
|
59
|
-
field = hash.keys.join(", ")
|
60
|
-
value = hash.values.join(", ")
|
61
|
-
when /\AFunction for field ([\w\_]+)\:\z/
|
62
|
-
type = "func"
|
63
|
-
operation = "func"
|
64
|
-
field = $1
|
65
|
-
when /\AqueryWeight\, product of\:\z/
|
66
|
-
type = "queryWeight"
|
67
|
-
operation = "queryWeight"
|
68
|
-
operator = "x"
|
69
|
-
when /\AfieldWeight in \d+\, product of\:\z/
|
70
|
-
type = "fieldWeight"
|
71
|
-
operation = "fieldWeight"
|
72
|
-
operator = "x"
|
73
|
-
when /\AqueryNorm/
|
74
|
-
type = "queryNorm"
|
75
|
-
operation = "queryNorm"
|
76
|
-
when /\Afunction score\, product of\:\z/,
|
77
|
-
/\Afunction score\, score mode \[multiply\]\z/
|
78
|
-
type = "func score"
|
79
|
-
operator = "x"
|
80
|
-
when /\Afunction score\, score mode \[sum\]\z/
|
81
|
-
type = "func score"
|
82
|
-
operator = "+"
|
83
|
-
when /\Ascript score function\, computed with script:\"(?<s>.+)\"\s*(?:and parameters:\s*(?<p>.+))?/m
|
84
|
-
type = "script"
|
85
|
-
operation = "script"
|
86
|
-
script, param = $~[:s], $~[:p]
|
87
|
-
script = script.gsub("\n", '')
|
88
|
-
script = "\"#{script}\""
|
89
|
-
param.gsub!("\n", '') if param
|
90
|
-
field = script.scan(/doc\[\'([\w\.]+)\'\]/).flatten.uniq.compact.join(" ")
|
91
|
-
value = [script, param].join(" ")
|
92
|
-
when /\AConstantScore\(.+\), product of\:\z/
|
93
|
-
type = "constant"
|
94
|
-
operation = "constant"
|
95
|
-
when "static boost factor", "boostFactor"
|
96
|
-
type = "boost"
|
97
|
-
operation = "boost"
|
98
|
-
when "product of:", "[multiply]"
|
99
|
-
type = "product"
|
100
|
-
operation = "product"
|
101
|
-
operator = "x"
|
102
|
-
when "Math.min of"
|
103
|
-
type = "min"
|
104
|
-
operator = "min"
|
105
|
-
when "Math.max of"
|
106
|
-
type = "max"
|
107
|
-
operator = "max"
|
108
|
-
when "sum of:"
|
109
|
-
type = "sum"
|
110
|
-
operator = "+"
|
111
|
-
when "maxBoost"
|
112
|
-
type = "maxBoost"
|
113
|
-
else
|
114
|
-
type = description
|
115
|
-
operation = description
|
26
|
+
def parse_details(node)
|
27
|
+
node.details.each do |detail|
|
28
|
+
child = create_node(detail, level: node.level.succ)
|
29
|
+
node.children << child
|
30
|
+
parse_details(child)
|
31
|
+
end
|
116
32
|
end
|
117
33
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
34
|
+
def parse_description(description)
|
35
|
+
case description
|
36
|
+
when /\Aweight\((\w+)\:(\w+)\s+in\s+\d+\)\s+\[\w+\]\, result of\:\z/
|
37
|
+
type = "weight"
|
38
|
+
operation = "weight"
|
39
|
+
operator = "x"
|
40
|
+
field = $1
|
41
|
+
value = $2
|
42
|
+
when /\Aidf\(docFreq\=(\d+)\, maxDocs\=(\d+)\)\z/
|
43
|
+
type = "idf"
|
44
|
+
operation = "idf(#{$1}/#{$2})"
|
45
|
+
when /\Atf\(freq\=([\d.]+)\)\, with freq of\:\z/
|
46
|
+
type = "tf"
|
47
|
+
operation = "tf(#{$1})"
|
48
|
+
when /\Ascore\(doc\=\d+\,freq=[\d\.]+\)\, product of\:\z/
|
49
|
+
type = "score"
|
50
|
+
operation = "score"
|
51
|
+
operator = "x"
|
52
|
+
when /\Amatch filter\: (?:cache\()?(?:(?<op>[\w]+)\()*(?<c>.+)\)*\z/
|
53
|
+
type = "match"
|
54
|
+
operation = "match"
|
55
|
+
operation += ".#{$~[:op]}" if $~[:op] && !%w[QueryWrapperFilter].include?($~[:op])
|
56
|
+
content = $~[:c]
|
57
|
+
content = content[0..-2] if content.end_with?(')')
|
58
|
+
hash = tokenize_contents(content)
|
59
|
+
field = hash.keys.join(", ")
|
60
|
+
value = hash.values.join(", ")
|
61
|
+
when /\AFunction for field ([\w\_]+)\:\z/
|
62
|
+
type = "func"
|
63
|
+
operation = "func"
|
64
|
+
field = $1
|
65
|
+
when /\AqueryWeight\, product of\:\z/
|
66
|
+
type = "queryWeight"
|
67
|
+
operation = "queryWeight"
|
68
|
+
operator = "x"
|
69
|
+
when /\AfieldWeight in \d+\, product of\:\z/
|
70
|
+
type = "fieldWeight"
|
71
|
+
operation = "fieldWeight"
|
72
|
+
operator = "x"
|
73
|
+
when /\AqueryNorm/
|
74
|
+
type = "queryNorm"
|
75
|
+
operation = "queryNorm"
|
76
|
+
when /\Afunction score\, product of\:\z/,
|
77
|
+
/\Afunction score\, score mode \[multiply\]\z/
|
78
|
+
type = "func score"
|
79
|
+
operator = "x"
|
80
|
+
when /\Afunction score\, score mode \[sum\]\z/
|
81
|
+
type = "func score"
|
82
|
+
operator = "+"
|
83
|
+
when /\Ascript score function\, computed with script:\"(?<s>.+)\"\s*(?:and parameters:\s*(?<p>.+))?/m
|
84
|
+
type = "script"
|
85
|
+
operation = "script"
|
86
|
+
script, param = $~[:s], $~[:p]
|
87
|
+
script = script.gsub("\n", '')
|
88
|
+
script = "\"#{script}\""
|
89
|
+
param.gsub!("\n", '') if param
|
90
|
+
field = script.scan(/doc\[\'([\w\.]+)\'\]/).flatten.uniq.compact.join(" ")
|
91
|
+
value = [script, param].join(" ")
|
92
|
+
when /\AConstantScore\(.+\), product of\:\z/
|
93
|
+
type = "constant"
|
94
|
+
operation = "constant"
|
95
|
+
when "static boost factor", "boostFactor"
|
96
|
+
type = "boost"
|
97
|
+
operation = "boost"
|
98
|
+
when /product\sof\:?/, "[multiply]"
|
99
|
+
type = "product"
|
100
|
+
operation = "product"
|
101
|
+
operator = "x"
|
102
|
+
when "Math.min of"
|
103
|
+
type = "min"
|
104
|
+
operator = "min"
|
105
|
+
when "Math.max of"
|
106
|
+
type = "max"
|
107
|
+
operator = "max"
|
108
|
+
when /sum of\:?/
|
109
|
+
type = "sum"
|
110
|
+
operator = "+"
|
111
|
+
when "maxBoost"
|
112
|
+
type = "maxBoost"
|
113
|
+
else
|
114
|
+
type = description
|
115
|
+
operation = description
|
116
|
+
end
|
117
|
+
|
118
|
+
Description.new(
|
119
|
+
raw: description,
|
120
|
+
type: type,
|
121
|
+
operator: operator,
|
122
|
+
operation: operation,
|
123
|
+
field: field,
|
124
|
+
value: value,
|
125
|
+
)
|
126
|
+
end
|
127
127
|
end
|
128
128
|
end
|
129
129
|
end
|
@@ -33,108 +33,17 @@ module Elasticsearch
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
def render_score(score)
|
41
|
-
value = if !@plain_score && score > 1_000
|
42
|
-
sprintf("%1.2g", score.round(2))
|
43
|
-
else
|
44
|
-
score.round(2).to_s
|
45
|
-
end
|
46
|
-
ansi(value, :magenta, :bright)
|
47
|
-
end
|
48
|
-
|
49
|
-
def render_details(node)
|
50
|
-
case node.type
|
51
|
-
when "func score"
|
52
|
-
render_boost_match_details(node)
|
53
|
-
else
|
54
|
-
render_node_details(node)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def render_node_details(node)
|
59
|
-
node.children.map do |child|
|
60
|
-
check_node = block_given? ? yield(child) : true
|
61
|
-
check_node && render_node(child) || nil
|
62
|
-
end.compact.join(" #{node.operator} ")
|
63
|
-
end
|
64
|
-
|
65
|
-
def render_boost_match_details(node)
|
66
|
-
match = node.children.find {|c| c.type == "match" }
|
67
|
-
boost = node.children.find {|c| c.type == "boost" }
|
68
|
-
if match && boost
|
69
|
-
[boost.score, render_node(match)].join(" x ")
|
70
|
-
else
|
71
|
-
render_node_details(node) {|n| !n.match_all? }
|
36
|
+
def render_result(node)
|
37
|
+
@buffer << " " * node.level * 2 + [render_score(node.score), "=", render_details(node)].flatten.join(" ")
|
72
38
|
end
|
73
|
-
end
|
74
39
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
if child.func?
|
79
|
-
render_node(child)
|
80
|
-
elsif child.children[0].match? && child.children[1].boost?
|
81
|
-
match = child.children[0]
|
82
|
-
boost = child.children[1]
|
83
|
-
"#{render_score(boost.score)}(#{render_description(match.description)})"
|
84
|
-
else
|
85
|
-
recursive_render_details(child)
|
86
|
-
end
|
40
|
+
def render_details(node)
|
41
|
+
if node.has_children?
|
42
|
+
node.children.map(&method(:render_node)).compact.join(" #{node.operator} ")
|
87
43
|
else
|
88
|
-
|
89
|
-
render_node(child)
|
90
|
-
end
|
44
|
+
render_node(node)
|
91
45
|
end
|
92
|
-
end.compact
|
93
|
-
|
94
|
-
if details.size > 1
|
95
|
-
wrap_paren(details.join(" #{node.operator} "))
|
96
|
-
else
|
97
|
-
details[0]
|
98
46
|
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def render_node(node)
|
102
|
-
text = render_score(node.score)
|
103
|
-
desc = render_description(node.description)
|
104
|
-
text = "#{text}(#{desc})" unless desc.empty?
|
105
|
-
text
|
106
|
-
end
|
107
|
-
|
108
|
-
def render_description(description)
|
109
|
-
text = ''
|
110
|
-
text = description.operation if description.operation
|
111
|
-
if description.field && description.value
|
112
|
-
if @show_values
|
113
|
-
text += "(#{field(description.field)}:#{value(description.value)})"
|
114
|
-
else
|
115
|
-
text += "(#{field(description.field)})"
|
116
|
-
end
|
117
|
-
elsif description.field
|
118
|
-
text += "(#{field(description.field)})"
|
119
|
-
end
|
120
|
-
text
|
121
|
-
end
|
122
|
-
|
123
|
-
def field(str)
|
124
|
-
ansi(str, :blue ,:bright)
|
125
|
-
end
|
126
|
-
|
127
|
-
def value(str)
|
128
|
-
ansi(str, :green)
|
129
|
-
end
|
130
|
-
|
131
|
-
def wrap_paren(string)
|
132
|
-
if string.start_with?("(") && string.end_with?(")")
|
133
|
-
string
|
134
|
-
else
|
135
|
-
"(" + string + ")"
|
136
|
-
end
|
137
|
-
end
|
138
47
|
end
|
139
48
|
end
|
140
49
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
-
require "elasticsearch/api/response/renderers/hash_renderer"
|
2
1
|
require "elasticsearch/api/response/explain_node"
|
3
2
|
require "elasticsearch/api/response/description"
|
4
3
|
require "elasticsearch/api/response/explain_parser"
|
5
|
-
require "elasticsearch/api/response/
|
4
|
+
require "elasticsearch/api/response/explain_trimmer"
|
5
|
+
require "elasticsearch/api/response/renderers/standard_renderer"
|
6
|
+
require "elasticsearch/api/response/renderers/inline_renderer"
|
7
|
+
require "elasticsearch/api/response/renderers/hash_renderer"
|
6
8
|
|
7
9
|
module Elasticsearch
|
8
10
|
module API
|
@@ -34,35 +36,43 @@ module Elasticsearch
|
|
34
36
|
def render(result, options = {})
|
35
37
|
new(result["explanation"], options).render
|
36
38
|
end
|
39
|
+
|
40
|
+
def result_as_hash(result, options = {})
|
41
|
+
new(result["explanation"], options).render_as_hash
|
42
|
+
end
|
37
43
|
end
|
38
44
|
|
39
|
-
attr_reader :explain
|
45
|
+
attr_reader :explain, :trim, :rendering_options
|
40
46
|
|
41
47
|
def initialize(explain, options = {})
|
42
48
|
@explain = explain
|
43
49
|
@indent = 0
|
44
|
-
@
|
50
|
+
@trim = options.has_key?(:trim) ? options.delete(:trim) : true
|
51
|
+
@rendering_options = options
|
52
|
+
|
53
|
+
parse_details
|
45
54
|
end
|
46
55
|
|
47
56
|
def render
|
48
|
-
|
49
|
-
@renderer.render(@root)
|
57
|
+
Renderers::StandardRenderer.new({ colorize: true }.merge(rendering_options)).render(@root)
|
50
58
|
end
|
51
59
|
|
52
60
|
def render_in_line
|
53
|
-
|
54
|
-
@renderer.render_in_line(@root)
|
61
|
+
Renderers::InlineRenderer.new({ colorize: true }.merge(rendering_options)).render(@root)
|
55
62
|
end
|
56
63
|
|
57
64
|
def render_as_hash
|
58
|
-
parse_details
|
59
65
|
Renderers::HashRenderer.new.render(@root)
|
60
66
|
end
|
61
67
|
|
62
68
|
private
|
63
69
|
|
64
70
|
def parse_details
|
65
|
-
@root ||=
|
71
|
+
@root ||= begin
|
72
|
+
tree = ExplainParser.new.parse(explain)
|
73
|
+
tree = ExplainTrimmer.new.trim(tree) if trim
|
74
|
+
tree
|
75
|
+
end
|
66
76
|
end
|
67
77
|
end
|
68
78
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module API
|
3
|
+
module Response
|
4
|
+
class ExplainTrimmer
|
5
|
+
def initialize
|
6
|
+
end
|
7
|
+
|
8
|
+
def trim(tree)
|
9
|
+
recursive_trim(tree)
|
10
|
+
end
|
11
|
+
|
12
|
+
def recursive_trim(node)
|
13
|
+
trim_node(node) if node.details.any?
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def trim_node(node)
|
19
|
+
case
|
20
|
+
when node.func_score?
|
21
|
+
trim_func_score_node(node)
|
22
|
+
when node.min?
|
23
|
+
trim_min_score_node(node)
|
24
|
+
else
|
25
|
+
trim_default_node(node)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def trim_func_score_node(node)
|
30
|
+
case node.children.size
|
31
|
+
when 2
|
32
|
+
match = node.children.find(&:match?)
|
33
|
+
if match
|
34
|
+
other = (node.children - [match])[0]
|
35
|
+
if match.score_one? && other.score == node.score
|
36
|
+
entity = match.field == "*" ? other : match
|
37
|
+
return merge_function_score_node(node, entity)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
trim_default_node(node)
|
43
|
+
end
|
44
|
+
|
45
|
+
def trim_default_node(node)
|
46
|
+
if node.has_children?
|
47
|
+
node.children = node.children.map(&method(:trim_node)).compact
|
48
|
+
end
|
49
|
+
node
|
50
|
+
end
|
51
|
+
|
52
|
+
# @note show only the node with a minimum score
|
53
|
+
def trim_min_score_node(node)
|
54
|
+
child = node.children.find {|n| n.score == node.score }
|
55
|
+
trim_node(child)
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge_function_score_node(current, entity)
|
59
|
+
ExplainNode.new(
|
60
|
+
score: current.score,
|
61
|
+
level: current.level,
|
62
|
+
details: current.details,
|
63
|
+
description: Description.new(
|
64
|
+
raw: current.description.raw,
|
65
|
+
type: entity.type,
|
66
|
+
operator: entity.operator,
|
67
|
+
operation: entity.operation,
|
68
|
+
field: entity.field,
|
69
|
+
value: entity.value
|
70
|
+
)
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module API
|
3
|
+
module Response
|
4
|
+
module Helpers
|
5
|
+
module ColorHelper
|
6
|
+
def colorized?
|
7
|
+
unless @ansi_loaded
|
8
|
+
@colorized = load_ansi
|
9
|
+
else
|
10
|
+
!!@colorized
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def disable_colorization
|
15
|
+
@ansi_loaded = true
|
16
|
+
@colorized = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_ansi
|
20
|
+
require "ansi/core"
|
21
|
+
true
|
22
|
+
rescue LoadError
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
def ansi(str, *codes)
|
27
|
+
if colorized?
|
28
|
+
str.to_s.ansi(*codes)
|
29
|
+
else
|
30
|
+
str.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Elasticsearch
|
2
|
+
module API
|
3
|
+
module Response
|
4
|
+
module Helpers
|
5
|
+
module StringHelper
|
6
|
+
WORD = /[\w\.\*]+/
|
7
|
+
WITH_QUOTE = /"[^"]*"/
|
8
|
+
WITH_BRACKET = /\[[^\]]*\]/
|
9
|
+
QUOTE_TOKENIZER = /(?:(?<field>#{WORD})(\:(?<value>(#{WORD}|#{WITH_QUOTE}|#{WITH_BRACKET})))?)+/
|
10
|
+
|
11
|
+
# @return [Hash] field name as a key and values as a value
|
12
|
+
def tokenize_contents(string)
|
13
|
+
string
|
14
|
+
.scan(QUOTE_TOKENIZER)
|
15
|
+
.each_with_object(Hash.new{|h,k| h[k] = []}) { |(field, value), memo|
|
16
|
+
memo[field] << value
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "elasticsearch/api/response/helpers/color_helper"
|
2
|
+
|
3
|
+
module Elasticsearch
|
4
|
+
module API
|
5
|
+
module Response
|
6
|
+
module Renderers
|
7
|
+
class BaseRenderer
|
8
|
+
include Helpers::ColorHelper
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
disable_colorization if options[:colorize] == false
|
12
|
+
@max = options[:max] || 3
|
13
|
+
@plain_score = options[:plain_score] == true
|
14
|
+
@show_values = options[:show_values] == true
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def render_score(score)
|
20
|
+
value = if !@plain_score && score > 1_000
|
21
|
+
sprintf("%1.2g", score.round(2))
|
22
|
+
else
|
23
|
+
score.round(2).to_s
|
24
|
+
end
|
25
|
+
ansi(value, :magenta, :bright)
|
26
|
+
end
|
27
|
+
|
28
|
+
def render_node(node)
|
29
|
+
text = render_score(node.score)
|
30
|
+
desc = render_description(node.description)
|
31
|
+
text = "#{text}(#{desc})" unless desc.empty?
|
32
|
+
text
|
33
|
+
end
|
34
|
+
|
35
|
+
def render_description(description)
|
36
|
+
text = ''
|
37
|
+
text = description.operation if description.operation
|
38
|
+
if description.field && description.value
|
39
|
+
if @show_values
|
40
|
+
text += "(#{field(description.field)}:#{value(description.value)})"
|
41
|
+
else
|
42
|
+
text += "(#{field(description.field)})"
|
43
|
+
end
|
44
|
+
elsif description.field
|
45
|
+
text += "(#{field(description.field)})"
|
46
|
+
end
|
47
|
+
text
|
48
|
+
end
|
49
|
+
|
50
|
+
def field(str)
|
51
|
+
ansi(str, :blue ,:bright)
|
52
|
+
end
|
53
|
+
|
54
|
+
def value(str)
|
55
|
+
ansi(str, :green)
|
56
|
+
end
|
57
|
+
|
58
|
+
def wrap_paren(string)
|
59
|
+
if string.start_with?("(") && string.end_with?(")")
|
60
|
+
string
|
61
|
+
else
|
62
|
+
"(" + string + ")"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -3,8 +3,6 @@ module Elasticsearch
|
|
3
3
|
module Response
|
4
4
|
module Renderers
|
5
5
|
class HashRenderer
|
6
|
-
def initialize
|
7
|
-
end
|
8
6
|
|
9
7
|
def render(tree)
|
10
8
|
recursive_render(tree)
|
@@ -16,24 +14,24 @@ module Elasticsearch
|
|
16
14
|
|
17
15
|
private
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
def format_node(node)
|
18
|
+
node.as_json.tap do |hash|
|
19
|
+
if node.has_children?
|
20
|
+
children = format_children(node, hash)
|
21
|
+
hash[:children] = children if children.any?
|
22
|
+
end
|
24
23
|
end
|
25
24
|
end
|
26
|
-
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
def format_children(node, hash)
|
27
|
+
node.children.map(&method(:format_node)).compact.tap do |children|
|
28
|
+
remove_dup(children, hash)
|
29
|
+
end
|
31
30
|
end
|
32
|
-
end
|
33
31
|
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
def remove_dup(collection, target)
|
33
|
+
collection.delete_if {|elm| elm == target }
|
34
|
+
end
|
37
35
|
end
|
38
36
|
end
|
39
37
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "elasticsearch/api/response/renderers/base_renderer"
|
2
|
+
|
3
|
+
module Elasticsearch
|
4
|
+
module API
|
5
|
+
module Response
|
6
|
+
module Renderers
|
7
|
+
class InlineRenderer < BaseRenderer
|
8
|
+
|
9
|
+
def render(tree)
|
10
|
+
[render_score(tree.score), "=", recursive_render_details(tree)].flatten.join(" ")
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def recursive_render_details(node)
|
16
|
+
details = node.children.map do |child|
|
17
|
+
if child.children.any? && child.level <= @max
|
18
|
+
recursive_render_details(child)
|
19
|
+
else
|
20
|
+
if !child.match_all?
|
21
|
+
render_node(child)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end.compact
|
25
|
+
|
26
|
+
if details.size > 1
|
27
|
+
wrap_paren(details.join(" #{node.operator} "))
|
28
|
+
else
|
29
|
+
details[0]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "elasticsearch/api/response/renderers/base_renderer"
|
2
|
+
|
3
|
+
module Elasticsearch
|
4
|
+
module API
|
5
|
+
module Response
|
6
|
+
module Renderers
|
7
|
+
|
8
|
+
class StandardRenderer < BaseRenderer
|
9
|
+
|
10
|
+
def render(tree)
|
11
|
+
@buffer = []
|
12
|
+
recursive_render(tree)
|
13
|
+
@buffer.join("\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def recursive_render(node)
|
19
|
+
return if node.level > @max
|
20
|
+
render_result(node) if node.details.any?
|
21
|
+
node.children.each do |child|
|
22
|
+
recursive_render(child)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def render_result(node)
|
27
|
+
@buffer << " " * node.level * 2 + [render_score(node.score), "=", render_details(node)].flatten.join(" ")
|
28
|
+
end
|
29
|
+
|
30
|
+
def render_details(node)
|
31
|
+
if node.has_children?
|
32
|
+
node.children.map(&method(:render_node)).compact.join(" #{node.operator} ")
|
33
|
+
else
|
34
|
+
render_node(node)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -41,7 +41,7 @@ describe Elasticsearch::API::Response::ExplainResponse do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it "returns summary of explain in line" do
|
44
|
-
expect(subject).to eq("0.05 = (
|
44
|
+
expect(subject).to eq("0.05 = (1.0(idf(2/3)) x 0.43(queryNorm)) x (1.0(tf(1.0)) x 1.0(idf(2/3)) x 0.25(fieldNorm(doc=0))) x 10.0(match(name.raw:smith)) x 0.99(func(updated_at)) x 5.93(script(popularity:\"def val = factor * log(sqrt(doc['popularity'].value) + 1) + 1\" {factor=1.0})) x 1.0(constant) x 0.5(coord(1/2)) x 1.0(queryBoost)")
|
45
45
|
end
|
46
46
|
|
47
47
|
context "with fake_response2" do
|
@@ -50,7 +50,7 @@ describe Elasticsearch::API::Response::ExplainResponse do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
it "returns summary of explain in line" do
|
53
|
-
expect(subject).to eq("887.19 = (
|
53
|
+
expect(subject).to eq("887.19 = (10.0(match(name:hawaii)) x 10.0(match(name:guam)) x 0.7(match(name:\"new caledonia\", new, nueva, caledonia)) x 3.0(match(with_beach:T)) x 0.99(func(updated_at)) x 3.0(match(region_id:[3 TO 3]))) x 1.0(queryBoost)")
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -61,7 +61,7 @@ describe Elasticsearch::API::Response::ExplainResponse do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
it "returns summary of explain in line" do
|
64
|
-
expect(subject).to eq("0.05 = (
|
64
|
+
expect(subject).to eq("0.05 = (1.0(idf(2/3)) x 0.43(queryNorm)) x (1.0(tf(1.0)) x 1.0(idf(2/3)) x 0.25(fieldNorm(doc=0))) x 10.0(match(name.raw)) x 0.99(func(updated_at)) x 5.93(script(popularity)) x 1.0(constant) x 0.5(coord(1/2)) x 1.0(queryBoost)")
|
65
65
|
end
|
66
66
|
|
67
67
|
context "with fake_response2" do
|
@@ -70,7 +70,7 @@ describe Elasticsearch::API::Response::ExplainResponse do
|
|
70
70
|
end
|
71
71
|
|
72
72
|
it "returns summary of explain in line" do
|
73
|
-
expect(subject).to eq("887.19 = (
|
73
|
+
expect(subject).to eq("887.19 = (10.0(match(name)) x 10.0(match(name)) x 0.7(match(name)) x 3.0(match(with_beach)) x 0.99(func(updated_at)) x 3.0(match(region_id))) x 1.0(queryBoost)")
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -95,7 +95,6 @@ describe Elasticsearch::API::Response::ExplainResponse do
|
|
95
95
|
it "returns summary of explain in lines" do
|
96
96
|
expect(subject).to match_array [
|
97
97
|
"0.05 = 0.11 x 0.5(coord(1/2)) x 1.0(queryBoost)",
|
98
|
-
" 0.11 = 0.11 min 3.4e+38",
|
99
98
|
" 0.11 = 0.11(weight(_all:smith))",
|
100
99
|
" 0.11 = 0.11(score)"
|
101
100
|
]
|
@@ -107,13 +106,12 @@ describe Elasticsearch::API::Response::ExplainResponse do
|
|
107
106
|
it "returns summary of explain in lines" do
|
108
107
|
expect(subject).to match_array([
|
109
108
|
"0.05 = 0.11 x 0.5(coord(1/2)) x 1.0(queryBoost)",
|
110
|
-
" 0.11 = 0.11 min 3.4e+38",
|
111
109
|
" 0.11 = 0.11(weight(_all:smith))",
|
112
110
|
" 0.11 = 0.11(score)",
|
113
|
-
" 0.11 = 0.43(queryWeight) x 0.25(fieldWeight) x 10.0 x 0.99 x 5.93(script(popularity:\"def val = factor * log(sqrt(doc['popularity'].value) + 1) + 1\" {factor=1.0})) x 1.0(constant)",
|
111
|
+
" 0.11 = 0.43(queryWeight) x 0.25(fieldWeight) x 10.0(match(name.raw:smith)) x 0.99(func(updated_at)) x 5.93(script(popularity:\"def val = factor * log(sqrt(doc['popularity'].value) + 1) + 1\" {factor=1.0})) x 1.0(constant)",
|
114
112
|
" 0.43 = 1.0(idf(2/3)) x 0.43(queryNorm)",
|
115
113
|
" 0.25 = 1.0(tf(1.0)) x 1.0(idf(2/3)) x 0.25(fieldNorm(doc=0))",
|
116
|
-
" 10.0 = 10.0
|
114
|
+
" 10.0 = 10.0(match(name.raw:smith))",
|
117
115
|
" 0.99 = 0.99(func(updated_at))"
|
118
116
|
])
|
119
117
|
end
|
@@ -125,7 +123,6 @@ describe Elasticsearch::API::Response::ExplainResponse do
|
|
125
123
|
it "returns summary of explain in lines" do
|
126
124
|
expect(subject).to match_array([
|
127
125
|
"0.05 = 0.11 x 0.5(coord(1/2)) x 1.0(queryBoost)",
|
128
|
-
" 0.11 = 0.11 min 3.4028235e+38",
|
129
126
|
" 0.11 = 0.11(weight(_all:smith))",
|
130
127
|
" 0.11 = 0.11(score)"
|
131
128
|
])
|
data/spec/fixtures/response2.yml
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elasticsearch-explain-response
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tomoya Hirano
|
@@ -109,14 +109,18 @@ files:
|
|
109
109
|
- circle.yml
|
110
110
|
- elasticsearch-explain-response.gemspec
|
111
111
|
- lib/elasticsearch-explain-response.rb
|
112
|
-
- lib/elasticsearch/api/response/color_helper.rb
|
113
112
|
- lib/elasticsearch/api/response/description.rb
|
114
113
|
- lib/elasticsearch/api/response/explain_node.rb
|
115
114
|
- lib/elasticsearch/api/response/explain_parser.rb
|
116
115
|
- lib/elasticsearch/api/response/explain_renderer.rb
|
117
116
|
- lib/elasticsearch/api/response/explain_response.rb
|
117
|
+
- lib/elasticsearch/api/response/explain_trimmer.rb
|
118
|
+
- lib/elasticsearch/api/response/helpers/color_helper.rb
|
119
|
+
- lib/elasticsearch/api/response/helpers/string_helper.rb
|
120
|
+
- lib/elasticsearch/api/response/renderers/base_renderer.rb
|
118
121
|
- lib/elasticsearch/api/response/renderers/hash_renderer.rb
|
119
|
-
- lib/elasticsearch/api/response/
|
122
|
+
- lib/elasticsearch/api/response/renderers/inline_renderer.rb
|
123
|
+
- lib/elasticsearch/api/response/renderers/standard_renderer.rb
|
120
124
|
- spec/elasticsearch/api/response/explain_response_spec.rb
|
121
125
|
- spec/fixtures/response1.yml
|
122
126
|
- spec/fixtures/response2.yml
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module Elasticsearch
|
2
|
-
module API
|
3
|
-
module Response
|
4
|
-
module ColorHelper
|
5
|
-
def colorized?
|
6
|
-
unless @ansi_loaded
|
7
|
-
@colorized = load_ansi
|
8
|
-
else
|
9
|
-
!!@colorized
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def disable_colorization
|
14
|
-
@ansi_loaded = true
|
15
|
-
@colorized = false
|
16
|
-
end
|
17
|
-
|
18
|
-
def load_ansi
|
19
|
-
require "ansi/core"
|
20
|
-
true
|
21
|
-
rescue LoadError
|
22
|
-
false
|
23
|
-
end
|
24
|
-
|
25
|
-
def ansi(str, *codes)
|
26
|
-
if colorized?
|
27
|
-
str.to_s.ansi(*codes)
|
28
|
-
else
|
29
|
-
str.to_s
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module Elasticsearch
|
2
|
-
module API
|
3
|
-
module Response
|
4
|
-
module StringHelper
|
5
|
-
WORD = /[\w\.\*]+/
|
6
|
-
WITH_QUOTE = /"[^"]*"/
|
7
|
-
WITH_BRACKET = /\[[^\]]*\]/
|
8
|
-
QUOTE_TOKENIZER = /(?:(?<field>#{WORD})(\:(?<value>(#{WORD}|#{WITH_QUOTE}|#{WITH_BRACKET})))?)+/
|
9
|
-
|
10
|
-
# @return [Hash] field name as a key and values as a value
|
11
|
-
def tokenize_contents(string)
|
12
|
-
string
|
13
|
-
.scan(QUOTE_TOKENIZER)
|
14
|
-
.each_with_object(Hash.new{|h,k| h[k] = []}) { |(field, value), memo|
|
15
|
-
memo[field] << value
|
16
|
-
}
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|