elasticsearch-explain-response 0.1.1 → 0.2.0
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/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
|