stretchy 0.5.3 → 0.8.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 +5 -5
- data/.rubocop.yml +260 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +12 -0
- data/Dockerfile +12 -0
- data/README.md +3 -3
- data/Rakefile +2 -2
- data/docker-compose.yml +24 -0
- data/lib/stretchy/and_collector.rb +27 -123
- data/lib/stretchy/api.rb +10 -30
- data/lib/stretchy/factory.rb +27 -33
- data/lib/stretchy/node.rb +4 -0
- data/lib/stretchy/results.rb +6 -2
- data/lib/stretchy/utils.rb +3 -1
- data/lib/stretchy/version.rb +1 -1
- data/lib/stretchy.rb +1 -1
- data/solano.yml +4 -4
- data/stretchy.gemspec +3 -4
- metadata +13 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 76d1eeb5c2be5aad546eed305e53d83ea4c64dd9c61e9b40a64ec0246de6c4cf
|
|
4
|
+
data.tar.gz: 4a6ddb55f798e90fde4ce5a1a5cb2b39559ca0eb2b5f9f92f353b0c108dfe224
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 58dd985f4bdee6edd189c6b5235d2cb59dd96a79c479fae3abae7f88a087f10c383e4c4eb183090c49c9619b2bc5dddad5ad8fdc6f80efc6a507be1dd8de609f
|
|
7
|
+
data.tar.gz: 055776e2bfb69e791c631df6576ed33c76337d74e64720c9ec86e8e79500248d2c37f2104f820aa32c978ee4ee6280dbfc5a9f54c8cfa809465456ee9401c97a
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
|
2
|
+
# on 2015-12-09 14:43:24 -0800 using RuboCop version 0.32.1.
|
|
3
|
+
# The point is for the user to remove these configuration records
|
|
4
|
+
# one by one as the offenses are removed from the code base.
|
|
5
|
+
# Note that changes in the inspected code, or installation of new
|
|
6
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
7
|
+
|
|
8
|
+
# Offense count: 1
|
|
9
|
+
# Cop supports --auto-correct.
|
|
10
|
+
Lint/BlockAlignment:
|
|
11
|
+
Enabled: false
|
|
12
|
+
|
|
13
|
+
# Offense count: 3
|
|
14
|
+
# Cop supports --auto-correct.
|
|
15
|
+
# Configuration parameters: AlignWith, SupportedStyles, AutoCorrect.
|
|
16
|
+
Lint/EndAlignment:
|
|
17
|
+
Enabled: false
|
|
18
|
+
|
|
19
|
+
# Offense count: 1
|
|
20
|
+
Lint/HandleExceptions:
|
|
21
|
+
Enabled: false
|
|
22
|
+
|
|
23
|
+
# Offense count: 3
|
|
24
|
+
# Cop supports --auto-correct.
|
|
25
|
+
Lint/UnusedBlockArgument:
|
|
26
|
+
Enabled: true
|
|
27
|
+
|
|
28
|
+
# Offense count: 2
|
|
29
|
+
Lint/UselessAssignment:
|
|
30
|
+
Enabled: true
|
|
31
|
+
|
|
32
|
+
# Offense count: 5
|
|
33
|
+
Metrics/AbcSize:
|
|
34
|
+
Max: 15
|
|
35
|
+
|
|
36
|
+
# Offense count: 2
|
|
37
|
+
# Configuration parameters: CountComments.
|
|
38
|
+
Metrics/ClassLength:
|
|
39
|
+
Max: 250
|
|
40
|
+
|
|
41
|
+
# Offense count: 1
|
|
42
|
+
Metrics/CyclomaticComplexity:
|
|
43
|
+
Max: 7
|
|
44
|
+
|
|
45
|
+
# Offense count: 3
|
|
46
|
+
# Configuration parameters: AllowURI, URISchemes.
|
|
47
|
+
Metrics/LineLength:
|
|
48
|
+
Max: 80
|
|
49
|
+
|
|
50
|
+
# Offense count: 8
|
|
51
|
+
# Configuration parameters: CountComments.
|
|
52
|
+
Metrics/MethodLength:
|
|
53
|
+
Max: 20
|
|
54
|
+
|
|
55
|
+
# Offense count: 2
|
|
56
|
+
# Configuration parameters: CountComments.
|
|
57
|
+
Metrics/ModuleLength:
|
|
58
|
+
Max: 250
|
|
59
|
+
|
|
60
|
+
# Offense count: 14
|
|
61
|
+
# Cop supports --auto-correct.
|
|
62
|
+
Style/Alias:
|
|
63
|
+
Enabled: false
|
|
64
|
+
|
|
65
|
+
# Offense count: 1
|
|
66
|
+
# Cop supports --auto-correct.
|
|
67
|
+
# Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles.
|
|
68
|
+
Style/AlignHash:
|
|
69
|
+
Enabled: false
|
|
70
|
+
|
|
71
|
+
# Offense count: 7
|
|
72
|
+
# Cop supports --auto-correct.
|
|
73
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods.
|
|
74
|
+
Style/BlockDelimiters:
|
|
75
|
+
Enabled: false
|
|
76
|
+
|
|
77
|
+
# Offense count: 3
|
|
78
|
+
# Cop supports --auto-correct.
|
|
79
|
+
Style/BlockEndNewline:
|
|
80
|
+
Enabled: false
|
|
81
|
+
|
|
82
|
+
# Offense count: 3
|
|
83
|
+
# Cop supports --auto-correct.
|
|
84
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
85
|
+
Style/BracesAroundHashParameters:
|
|
86
|
+
Enabled: false
|
|
87
|
+
|
|
88
|
+
# Offense count: 1
|
|
89
|
+
# Cop supports --auto-correct.
|
|
90
|
+
Style/ClosingParenthesisIndentation:
|
|
91
|
+
Enabled: false
|
|
92
|
+
|
|
93
|
+
# Offense count: 18
|
|
94
|
+
Style/Documentation:
|
|
95
|
+
Enabled: false
|
|
96
|
+
|
|
97
|
+
# Offense count: 3
|
|
98
|
+
Style/DoubleNegation:
|
|
99
|
+
Enabled: false
|
|
100
|
+
|
|
101
|
+
# Offense count: 5
|
|
102
|
+
# Cop supports --auto-correct.
|
|
103
|
+
Style/ElseAlignment:
|
|
104
|
+
Enabled: false
|
|
105
|
+
|
|
106
|
+
# Offense count: 16
|
|
107
|
+
# Cop supports --auto-correct.
|
|
108
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
109
|
+
Style/EmptyLinesAroundBlockBody:
|
|
110
|
+
Enabled: false
|
|
111
|
+
|
|
112
|
+
# Offense count: 6
|
|
113
|
+
# Cop supports --auto-correct.
|
|
114
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
115
|
+
Style/EmptyLinesAroundClassBody:
|
|
116
|
+
Enabled: false
|
|
117
|
+
|
|
118
|
+
# Offense count: 6
|
|
119
|
+
# Cop supports --auto-correct.
|
|
120
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
121
|
+
Style/EmptyLinesAroundModuleBody:
|
|
122
|
+
Enabled: false
|
|
123
|
+
|
|
124
|
+
# Offense count: 4
|
|
125
|
+
# Cop supports --auto-correct.
|
|
126
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
127
|
+
Style/FirstParameterIndentation:
|
|
128
|
+
Enabled: false
|
|
129
|
+
|
|
130
|
+
# Offense count: 1
|
|
131
|
+
# Cop supports --auto-correct.
|
|
132
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues.
|
|
133
|
+
Style/HashSyntax:
|
|
134
|
+
Enabled: false
|
|
135
|
+
|
|
136
|
+
# Offense count: 16
|
|
137
|
+
# Cop supports --auto-correct.
|
|
138
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
139
|
+
Style/IndentHash:
|
|
140
|
+
Enabled: false
|
|
141
|
+
|
|
142
|
+
# Offense count: 18
|
|
143
|
+
# Cop supports --auto-correct.
|
|
144
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
145
|
+
Style/IndentationConsistency:
|
|
146
|
+
Enabled: false
|
|
147
|
+
|
|
148
|
+
# Offense count: 3
|
|
149
|
+
# Cop supports --auto-correct.
|
|
150
|
+
# Configuration parameters: Width.
|
|
151
|
+
Style/IndentationWidth:
|
|
152
|
+
Enabled: false
|
|
153
|
+
|
|
154
|
+
# Offense count: 1
|
|
155
|
+
# Cop supports --auto-correct.
|
|
156
|
+
Style/LineEndConcatenation:
|
|
157
|
+
Enabled: false
|
|
158
|
+
|
|
159
|
+
# Offense count: 3
|
|
160
|
+
# Cop supports --auto-correct.
|
|
161
|
+
Style/MultilineBlockLayout:
|
|
162
|
+
Enabled: false
|
|
163
|
+
|
|
164
|
+
# Offense count: 10
|
|
165
|
+
# Cop supports --auto-correct.
|
|
166
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
167
|
+
Style/MultilineOperationIndentation:
|
|
168
|
+
Enabled: false
|
|
169
|
+
|
|
170
|
+
# Offense count: 2
|
|
171
|
+
# Cop supports --auto-correct.
|
|
172
|
+
# Configuration parameters: PreferredDelimiters.
|
|
173
|
+
Style/PercentLiteralDelimiters:
|
|
174
|
+
Enabled: false
|
|
175
|
+
|
|
176
|
+
# Offense count: 1
|
|
177
|
+
# Configuration parameters: NamePrefix, NamePrefixBlacklist.
|
|
178
|
+
Style/PredicateName:
|
|
179
|
+
Enabled: false
|
|
180
|
+
|
|
181
|
+
# Offense count: 3
|
|
182
|
+
# Configuration parameters: SupportedStyles.
|
|
183
|
+
Style/RaiseArgs:
|
|
184
|
+
EnforcedStyle: compact
|
|
185
|
+
|
|
186
|
+
# Offense count: 1
|
|
187
|
+
# Cop supports --auto-correct.
|
|
188
|
+
Style/RedundantSelf:
|
|
189
|
+
Enabled: false
|
|
190
|
+
|
|
191
|
+
# Offense count: 4
|
|
192
|
+
# Cop supports --auto-correct.
|
|
193
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
194
|
+
Style/SignalException:
|
|
195
|
+
Enabled: false
|
|
196
|
+
|
|
197
|
+
# Offense count: 4
|
|
198
|
+
# Cop supports --auto-correct.
|
|
199
|
+
Style/SingleSpaceBeforeFirstArg:
|
|
200
|
+
Enabled: false
|
|
201
|
+
|
|
202
|
+
# Offense count: 9
|
|
203
|
+
# Cop supports --auto-correct.
|
|
204
|
+
Style/SpaceAfterComma:
|
|
205
|
+
Enabled: false
|
|
206
|
+
|
|
207
|
+
# Offense count: 6
|
|
208
|
+
# Cop supports --auto-correct.
|
|
209
|
+
# Configuration parameters: MultiSpaceAllowedForOperators.
|
|
210
|
+
Style/SpaceAroundOperators:
|
|
211
|
+
Enabled: false
|
|
212
|
+
|
|
213
|
+
# Offense count: 5
|
|
214
|
+
# Cop supports --auto-correct.
|
|
215
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
216
|
+
Style/SpaceBeforeBlockBraces:
|
|
217
|
+
Enabled: false
|
|
218
|
+
|
|
219
|
+
# Offense count: 1
|
|
220
|
+
# Cop supports --auto-correct.
|
|
221
|
+
Style/SpaceBeforeSemicolon:
|
|
222
|
+
Enabled: false
|
|
223
|
+
|
|
224
|
+
# Offense count: 25
|
|
225
|
+
# Cop supports --auto-correct.
|
|
226
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
|
|
227
|
+
Style/SpaceInsideBlockBraces:
|
|
228
|
+
Enabled: false
|
|
229
|
+
|
|
230
|
+
# Offense count: 145
|
|
231
|
+
# Cop supports --auto-correct.
|
|
232
|
+
# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles.
|
|
233
|
+
Style/SpaceInsideHashLiteralBraces:
|
|
234
|
+
Enabled: false
|
|
235
|
+
|
|
236
|
+
# Offense count: 33
|
|
237
|
+
# Cop supports --auto-correct.
|
|
238
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
|
239
|
+
Style/StringLiterals:
|
|
240
|
+
Enabled: false
|
|
241
|
+
|
|
242
|
+
# Offense count: 1
|
|
243
|
+
# Cop supports --auto-correct.
|
|
244
|
+
# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
|
|
245
|
+
Style/TrivialAccessors:
|
|
246
|
+
Enabled: false
|
|
247
|
+
|
|
248
|
+
# Offense count: 2
|
|
249
|
+
# Cop supports --auto-correct.
|
|
250
|
+
Style/UnneededPercentQ:
|
|
251
|
+
Enabled: false
|
|
252
|
+
|
|
253
|
+
# Align with the style guide.
|
|
254
|
+
Style/CollectionMethods:
|
|
255
|
+
PreferredMethods:
|
|
256
|
+
collect: 'map'
|
|
257
|
+
collect!: 'map!'
|
|
258
|
+
inject: 'reduce'
|
|
259
|
+
detect: 'find'
|
|
260
|
+
find_all: 'select'
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.4.2
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
All notable changes to this project will be documented in this file.
|
|
3
|
+
This project adheres to [Semantic Versioning](http://semver.org/), although
|
|
4
|
+
"minor" (0.x) changes will include breaking changes until a stable 1.0 release.
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.6.0] - 2015-12-15
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
* [FEATURE] compatible with [new query / filter contexts](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html) for Elasticsearch 2.0+
|
|
12
|
+
|
data/Dockerfile
ADDED
data/README.md
CHANGED
|
@@ -52,7 +52,7 @@ Stretchy.client = Elasticsearch::Client.new
|
|
|
52
52
|
|
|
53
53
|
```ruby
|
|
54
54
|
# returns a Stretchy::API object
|
|
55
|
-
api = Stretchy.query(index: 'myapp_development'
|
|
55
|
+
api = Stretchy.query(index: 'myapp_development')
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
From here, you can chain the methods to build your desired query.
|
|
@@ -219,7 +219,7 @@ Performs a query for the given string anywhere in the document. At least one of
|
|
|
219
219
|
api = api.more_like(ids: [1, 2, 3])
|
|
220
220
|
.more_like(docs: other_search.results)
|
|
221
221
|
.more_like(
|
|
222
|
-
|
|
222
|
+
like: 'puppies and kittens are great',
|
|
223
223
|
fields: ['about_me']
|
|
224
224
|
)
|
|
225
225
|
```
|
|
@@ -436,7 +436,7 @@ Included in the Results object for Kaminari compatibility.
|
|
|
436
436
|
|
|
437
437
|
## Development
|
|
438
438
|
|
|
439
|
-
After checking out the repo, run `bundle install` to install dependencies. Then, run `pry` for an interactive prompt that will allow you to experiment.
|
|
439
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `pry` for an interactive prompt that will allow you to experiment. Run specs with `rspec`
|
|
440
440
|
|
|
441
441
|
## Contributing
|
|
442
442
|
|
data/Rakefile
CHANGED
|
@@ -13,8 +13,8 @@ namespace :fixtures do
|
|
|
13
13
|
task :gen do
|
|
14
14
|
require 'json'
|
|
15
15
|
|
|
16
|
-
q = Stretchy.query(index: 'stretchy_test'
|
|
17
|
-
.
|
|
16
|
+
q = Stretchy.query(index: 'stretchy_test')
|
|
17
|
+
.multi_match(query: 'game')
|
|
18
18
|
.where(url_slug: [
|
|
19
19
|
'masahiro-sakurai',
|
|
20
20
|
'tetsuya-mizuguchi',
|
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
version: '2'
|
|
2
|
+
services:
|
|
3
|
+
elastictwo:
|
|
4
|
+
image: elasticsearch:2-alpine
|
|
5
|
+
elasticfive:
|
|
6
|
+
image: elasticsearch:5-alpine
|
|
7
|
+
testtwo:
|
|
8
|
+
build: .
|
|
9
|
+
command: bundle exec rake
|
|
10
|
+
environment:
|
|
11
|
+
ELASTICSEARCH_URL: 'elastictwo:9200'
|
|
12
|
+
volumes:
|
|
13
|
+
- .:/stretchy
|
|
14
|
+
links:
|
|
15
|
+
- elastictwo
|
|
16
|
+
testfive:
|
|
17
|
+
build: .
|
|
18
|
+
command: bundle exec rake
|
|
19
|
+
environment:
|
|
20
|
+
ELASTICSEARCH_URL: 'elasticfive:9200'
|
|
21
|
+
volumes:
|
|
22
|
+
- .:/stretchy
|
|
23
|
+
links:
|
|
24
|
+
- elastictwo
|
|
@@ -22,138 +22,65 @@ module Stretchy
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def node
|
|
25
|
-
@node ||= if
|
|
25
|
+
@node ||= if function_score_node?
|
|
26
26
|
function_score_node
|
|
27
|
-
elsif filter_nodes.any?
|
|
28
|
-
filtered_query_node
|
|
29
27
|
elsif query_nodes.any?
|
|
30
|
-
|
|
28
|
+
query_node
|
|
31
29
|
else
|
|
32
30
|
Node.new({match_all: {}}, context)
|
|
33
31
|
end
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
def query_nodes
|
|
37
|
-
@query_nodes ||=
|
|
38
|
-
n.context?(:query) &&
|
|
39
|
-
!n.context?(:boost) &&
|
|
40
|
-
!n.context?(:filter)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def filter_node
|
|
45
|
-
@filter_node ||= if query_nodes.any?
|
|
46
|
-
if boost_nodes.any?
|
|
47
|
-
Node.new({query: function_score_node.json}, context)
|
|
48
|
-
elsif filter_nodes.any?
|
|
49
|
-
Node.new({query: filtered_query_node.json}, context)
|
|
50
|
-
else
|
|
51
|
-
Node.new({query: single_query_node.json}, context)
|
|
52
|
-
end
|
|
53
|
-
else
|
|
54
|
-
Node.new(compile_nodes(filter_nodes).json, context)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def filter_json
|
|
59
|
-
filter_node.json
|
|
35
|
+
@query_nodes ||= nodes.reject {|n| n.context? :boost }
|
|
60
36
|
end
|
|
61
37
|
|
|
62
|
-
def
|
|
63
|
-
@
|
|
64
|
-
node_arr = collect_nodes nodes do |n|
|
|
65
|
-
n.context?(:filter) &&
|
|
66
|
-
!n.context?(:query) &&
|
|
67
|
-
!n.context?(:boost)
|
|
68
|
-
end
|
|
69
|
-
node_arr += Array(compile_query_filter_node)
|
|
70
|
-
node_arr.compact
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def query_filter_nodes
|
|
75
|
-
@query_filter_nodes ||= collect_nodes nodes do |n|
|
|
76
|
-
n.context?(:filter) &&
|
|
77
|
-
n.context?(:query) &&
|
|
78
|
-
!n.context?(:boost)
|
|
79
|
-
end
|
|
38
|
+
def boost_nodes
|
|
39
|
+
@boost_nodes ||= nodes.select {|n| n.context? :boost }
|
|
80
40
|
end
|
|
81
41
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
n.context?(:boost)
|
|
85
|
-
end
|
|
42
|
+
def function_score_node?
|
|
43
|
+
boost_nodes.reject { |n| n.empty? }.any?
|
|
86
44
|
end
|
|
87
45
|
|
|
88
46
|
private
|
|
89
47
|
|
|
90
|
-
def
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if n.respond_to? :node
|
|
96
|
-
coll << Node.new(n.node.json, n.context)
|
|
97
|
-
else
|
|
98
|
-
coll << n
|
|
99
|
-
end
|
|
48
|
+
def query_node
|
|
49
|
+
if query_nodes.size > 1 || multicontext?(query_nodes)
|
|
50
|
+
compile_bool query_nodes
|
|
51
|
+
else
|
|
52
|
+
query_nodes.first
|
|
100
53
|
end
|
|
101
|
-
coll.compact
|
|
102
54
|
end
|
|
103
55
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
node_arr.any?{|n| n.context?(:must_not) || n.context?(:should)}
|
|
107
|
-
|
|
108
|
-
compile_bool(node_arr)
|
|
109
|
-
else
|
|
110
|
-
node_arr.first
|
|
111
|
-
end
|
|
56
|
+
def multicontext?(node_arr)
|
|
57
|
+
Array(node_arr).any? {|n| n.context?(:must_not) || n.context?(:should) }
|
|
112
58
|
end
|
|
113
59
|
|
|
114
60
|
def compile_bool(bool_nodes)
|
|
115
61
|
split_nodes = split_nodes_for_bool(bool_nodes)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
bool_json[:should] = [
|
|
119
|
-
{
|
|
120
|
-
bool: {
|
|
121
|
-
must: split_nodes[:should].map(&:as_json),
|
|
122
|
-
must_not: split_nodes[:should_not].map(&:as_json)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
]
|
|
126
|
-
else
|
|
127
|
-
bool_json[:should] = split_nodes[:should].map(&:as_json)
|
|
62
|
+
refined = bool_ctx.each_with_object(split_nodes) do |k, hash|
|
|
63
|
+
hash[k] = Array(compile_bool(hash[k])) if multicontext? hash[k]
|
|
128
64
|
end
|
|
129
|
-
bool_json
|
|
130
|
-
bool_json[:must] = split_nodes[:must].map(&:as_json)
|
|
65
|
+
bool_json = Hash[refined.map{|k,v| [k, v.map(&:as_json)] }]
|
|
131
66
|
Node.new(bool: bool_json)
|
|
132
67
|
end
|
|
133
68
|
|
|
69
|
+
def bool_ctx
|
|
70
|
+
[:filter, :must_not, :should]
|
|
71
|
+
end
|
|
72
|
+
|
|
134
73
|
def split_nodes_for_bool(bool_nodes)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
split_nodes[:should_not] << n
|
|
140
|
-
else
|
|
141
|
-
split_nodes[:should] << n
|
|
142
|
-
end
|
|
143
|
-
else
|
|
144
|
-
if n.context?(:must_not)
|
|
145
|
-
split_nodes[:must_not] << n
|
|
146
|
-
else
|
|
147
|
-
split_nodes[:must] << n
|
|
148
|
-
end
|
|
149
|
-
end
|
|
74
|
+
bool_nodes.each_with_object({}) do |n, hash|
|
|
75
|
+
key = bool_ctx.find{|c| n.context? c } || :must
|
|
76
|
+
hash[key] ||= []
|
|
77
|
+
hash[key] << Node.new(n.json, n.context.merge(key => nil))
|
|
150
78
|
end
|
|
151
|
-
split_nodes
|
|
152
79
|
end
|
|
153
80
|
|
|
154
81
|
def compile_boost_functions
|
|
155
82
|
boost_nodes.map do |n|
|
|
156
|
-
next
|
|
83
|
+
next if n.empty?
|
|
157
84
|
n.json
|
|
158
85
|
end.compact
|
|
159
86
|
end
|
|
@@ -164,36 +91,13 @@ module Stretchy
|
|
|
164
91
|
end
|
|
165
92
|
end
|
|
166
93
|
|
|
167
|
-
def compile_query_filter_node
|
|
168
|
-
compiled = compile_nodes(query_filter_nodes)
|
|
169
|
-
Node.new(query: compiled.json) if compiled
|
|
170
|
-
end
|
|
171
|
-
|
|
172
94
|
def function_score_node
|
|
173
95
|
function_score_json = compile_function_score_options
|
|
174
96
|
function_score_json[:functions] = compile_boost_functions
|
|
175
|
-
|
|
176
|
-
if query_nodes.any?
|
|
177
|
-
function_score_json[:query] = filtered_query_node.json
|
|
178
|
-
elsif filter_nodes.any?
|
|
179
|
-
function_score_json[:filter] = filter_node.json
|
|
180
|
-
end
|
|
97
|
+
function_score_json[:query] = query_node.json if query_nodes.any?
|
|
181
98
|
|
|
182
99
|
Node.new({function_score: function_score_json}, context)
|
|
183
100
|
end
|
|
184
101
|
|
|
185
|
-
def filtered_query_node
|
|
186
|
-
filtered_json = {}
|
|
187
|
-
q = compile_nodes(query_nodes)
|
|
188
|
-
f = compile_nodes(filter_nodes)
|
|
189
|
-
filtered_json[:query] = q.json if q
|
|
190
|
-
filtered_json[:filter] = f.json if f
|
|
191
|
-
Node.new({filtered: filtered_json}, context)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def single_query_node
|
|
195
|
-
Node.new(compile_nodes(query_nodes).json, context)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
102
|
end
|
|
199
103
|
end
|
data/lib/stretchy/api.rb
CHANGED
|
@@ -11,19 +11,11 @@ module Stretchy
|
|
|
11
11
|
|
|
12
12
|
attr_reader :collector, :opts, :root, :body, :context
|
|
13
13
|
|
|
14
|
+
delegate [:with_context, :json, :as_json] => :collector
|
|
15
|
+
|
|
14
16
|
delegate [
|
|
15
|
-
:total,
|
|
16
|
-
:
|
|
17
|
-
:length,
|
|
18
|
-
:size,
|
|
19
|
-
:total_pages,
|
|
20
|
-
:results,
|
|
21
|
-
:hits,
|
|
22
|
-
:to_a,
|
|
23
|
-
:ids,
|
|
24
|
-
:scores,
|
|
25
|
-
:explanations,
|
|
26
|
-
:aggregations
|
|
17
|
+
:total, :total_count, :length, :size, :total_pages, :results, :hits,
|
|
18
|
+
:to_a, :ids, :scores, :explanations, :aggregations, :each
|
|
27
19
|
] => :results_obj
|
|
28
20
|
|
|
29
21
|
def initialize(opts = {})
|
|
@@ -74,9 +66,11 @@ module Stretchy
|
|
|
74
66
|
end
|
|
75
67
|
|
|
76
68
|
def fields(*list)
|
|
77
|
-
add_root
|
|
69
|
+
add_root _source: list
|
|
78
70
|
end
|
|
79
71
|
|
|
72
|
+
alias :source :fields
|
|
73
|
+
|
|
80
74
|
def aggs(params = {})
|
|
81
75
|
add_body aggs: params
|
|
82
76
|
end
|
|
@@ -130,20 +124,19 @@ module Stretchy
|
|
|
130
124
|
end
|
|
131
125
|
|
|
132
126
|
def range(params = {})
|
|
133
|
-
require_context!
|
|
134
127
|
add_params params, nil, :range_node
|
|
135
128
|
end
|
|
136
129
|
|
|
137
130
|
def geo_distance(params = {})
|
|
138
|
-
add_params params,
|
|
131
|
+
add_params params, nil, :geo_distance_node
|
|
139
132
|
end
|
|
140
133
|
|
|
141
134
|
def boost(params = {}, options = {})
|
|
142
|
-
return add_context(:boost)
|
|
135
|
+
return add_context(:boost) if Utils.is_empty? params
|
|
143
136
|
|
|
144
137
|
subcontext = context.merge(boost: true)
|
|
145
138
|
if params.is_a? self.class
|
|
146
|
-
boost_json = options.merge(filter: params.
|
|
139
|
+
boost_json = options.merge(filter: params.json)
|
|
147
140
|
add_nodes Node.new(boost_json, subcontext)
|
|
148
141
|
else
|
|
149
142
|
add_nodes Factory.raw_boost_node(params, subcontext)
|
|
@@ -184,21 +177,8 @@ module Stretchy
|
|
|
184
177
|
results_obj.ids.count
|
|
185
178
|
end
|
|
186
179
|
|
|
187
|
-
def method_missing(method, *args, &block)
|
|
188
|
-
if collector.respond_to?(method)
|
|
189
|
-
collector.send(method, *args, &block)
|
|
190
|
-
else
|
|
191
|
-
super
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
180
|
private
|
|
196
181
|
|
|
197
|
-
def require_context!
|
|
198
|
-
return true if context?(:query) || context?(:filter)
|
|
199
|
-
raise 'You must specify either query or filter context'
|
|
200
|
-
end
|
|
201
|
-
|
|
202
182
|
def args_to_context(*args)
|
|
203
183
|
args.reduce({}) do |ctx, item|
|
|
204
184
|
next ctx if item.nil?
|
data/lib/stretchy/factory.rb
CHANGED
|
@@ -23,9 +23,7 @@ module Stretchy
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def extract_boost_params!(params)
|
|
26
|
-
|
|
27
|
-
boost_params = {weight: DEFAULT_WEIGHT} unless boost_params.any?
|
|
28
|
-
boost_params
|
|
26
|
+
Utils.extract_options!(params, BOOST_OPTIONS)
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
def extract_function_score_options!(params)
|
|
@@ -53,19 +51,17 @@ module Stretchy
|
|
|
53
51
|
context[:fn_score] = extract_function_score_options!(params)
|
|
54
52
|
context[:boost] = true
|
|
55
53
|
context[:filter] = true
|
|
56
|
-
|
|
57
|
-
Node.new(boost_params
|
|
54
|
+
boost_params.merge!(filter: params) unless Utils.is_empty? params
|
|
55
|
+
Node.new(boost_params, context)
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
def context_nodes(params, context = default_context)
|
|
61
|
-
subparams = dotify_params(params, context)
|
|
62
|
-
|
|
63
59
|
if context[:boost]
|
|
64
|
-
params_to_boost(
|
|
65
|
-
elsif context[:query]
|
|
66
|
-
|
|
60
|
+
params_to_boost(params, context)
|
|
61
|
+
elsif context[:filter] && !context[:query]
|
|
62
|
+
params_to_filters(dotify_params(params, context), context)
|
|
67
63
|
else
|
|
68
|
-
|
|
64
|
+
params_to_queries(dotify_params(params, context), context)
|
|
69
65
|
end
|
|
70
66
|
end
|
|
71
67
|
|
|
@@ -76,14 +72,12 @@ module Stretchy
|
|
|
76
72
|
nodes = context_nodes(params, subcontext)
|
|
77
73
|
collector = AndCollector.new(nodes, subcontext)
|
|
78
74
|
|
|
79
|
-
if
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Node.new(
|
|
83
|
-
boost_params.merge(filter: collector.filter_node.json),
|
|
84
|
-
context
|
|
85
|
-
)
|
|
75
|
+
boost_params.merge!(filter: collector.json) if collector.any?
|
|
76
|
+
if boost_params.count == 1 && boost_params.key?(:filter)
|
|
77
|
+
boost_params[:weight] = DEFAULT_WEIGHT
|
|
86
78
|
end
|
|
79
|
+
|
|
80
|
+
Node.new(boost_params, context)
|
|
87
81
|
end
|
|
88
82
|
|
|
89
83
|
def params_to_queries(params, context = default_context)
|
|
@@ -100,7 +94,11 @@ module Stretchy
|
|
|
100
94
|
when Hash
|
|
101
95
|
nested(val, field, context)
|
|
102
96
|
else
|
|
103
|
-
|
|
97
|
+
if field == '_all'
|
|
98
|
+
Node.new({multi_match: {:query => val}}, context)
|
|
99
|
+
else
|
|
100
|
+
Node.new({match: {field => val}}, context)
|
|
101
|
+
end
|
|
104
102
|
end
|
|
105
103
|
end
|
|
106
104
|
end
|
|
@@ -111,7 +109,8 @@ module Stretchy
|
|
|
111
109
|
when Range
|
|
112
110
|
Node.new({range: {field => {gte: val.min, lte: val.max}}}, context)
|
|
113
111
|
when nil
|
|
114
|
-
|
|
112
|
+
nil_ctx = context.merge(must_not: true)
|
|
113
|
+
Node.new({exists: {field: field}}, nil_ctx)
|
|
115
114
|
when Hash
|
|
116
115
|
nested(val, field, context)
|
|
117
116
|
else
|
|
@@ -121,19 +120,16 @@ module Stretchy
|
|
|
121
120
|
end
|
|
122
121
|
|
|
123
122
|
def nested(params, path, context = default_context)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
json = AndCollector.new(nodes, context).json
|
|
127
|
-
[:query, json]
|
|
123
|
+
nodes = if context[:filter] && !context[:query]
|
|
124
|
+
params_to_filters(params, context)
|
|
128
125
|
else
|
|
129
|
-
|
|
130
|
-
json = AndCollector.new(nodes, context).filter_json
|
|
131
|
-
[:filter, json]
|
|
126
|
+
params_to_queries(params, context)
|
|
132
127
|
end
|
|
128
|
+
json = AndCollector.new(nodes, context).json
|
|
133
129
|
|
|
134
130
|
Node.new({nested: {
|
|
135
131
|
path: path,
|
|
136
|
-
|
|
132
|
+
query: json
|
|
137
133
|
}}, context)
|
|
138
134
|
end
|
|
139
135
|
|
|
@@ -141,18 +137,16 @@ module Stretchy
|
|
|
141
137
|
def fulltext_nodes_from_string(params, context = default_context)
|
|
142
138
|
subcontext = context.merge(query: true)
|
|
143
139
|
nodes = [raw_node({
|
|
144
|
-
|
|
145
|
-
_all: {
|
|
140
|
+
multi_match: {
|
|
146
141
|
query: params,
|
|
147
142
|
minimum_should_match: 1
|
|
148
143
|
}
|
|
149
|
-
}
|
|
150
144
|
}, subcontext)]
|
|
151
145
|
|
|
152
146
|
subcontext = subcontext.merge(should: true)
|
|
153
147
|
nodes << Factory.raw_node({
|
|
154
148
|
match_phrase: {
|
|
155
|
-
|
|
149
|
+
multi_match: {
|
|
156
150
|
query: params,
|
|
157
151
|
slop: DEFAULT_SLOP
|
|
158
152
|
}
|
|
@@ -188,7 +182,7 @@ module Stretchy
|
|
|
188
182
|
|
|
189
183
|
# https://www.elastic.co/guide/en/elasticsearch/reference/current/querydslfunctionscorequery.html#functionrandom
|
|
190
184
|
def random_score_function_node(params, context = default_context)
|
|
191
|
-
json = {random_score: {seed: params[:seed]}}
|
|
185
|
+
json = {random_score: {seed: params[:seed], field: :id}}
|
|
192
186
|
json[:weight] = params[:weight] if params[:weight]
|
|
193
187
|
Node.new(json, context)
|
|
194
188
|
end
|
data/lib/stretchy/node.rb
CHANGED
data/lib/stretchy/results.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'json'
|
|
1
2
|
module Stretchy
|
|
2
3
|
class Results
|
|
3
4
|
|
|
@@ -12,7 +13,10 @@ module Stretchy
|
|
|
12
13
|
def self.fake
|
|
13
14
|
self.new(
|
|
14
15
|
{size: API::DEFAULT_PER_PAGE, fake: true},
|
|
15
|
-
{'hits' => {'total' =>
|
|
16
|
+
{'hits' => {'total' => {
|
|
17
|
+
"value" => 3,
|
|
18
|
+
"relation" => "eq"
|
|
19
|
+
}, 'hits' => [], 'aggregations' => {}}}
|
|
16
20
|
)
|
|
17
21
|
end
|
|
18
22
|
|
|
@@ -43,7 +47,7 @@ module Stretchy
|
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
def total
|
|
46
|
-
response['hits']['total']
|
|
50
|
+
response['hits']['total']['value']
|
|
47
51
|
end
|
|
48
52
|
alias :total_count :total
|
|
49
53
|
alias :count :total
|
data/lib/stretchy/utils.rb
CHANGED
|
@@ -5,7 +5,9 @@ module Stretchy
|
|
|
5
5
|
# detects empty string, empty array, empty hash, nil
|
|
6
6
|
def is_empty?(arg = nil)
|
|
7
7
|
return true if arg.nil?
|
|
8
|
-
if arg.respond_to?(:
|
|
8
|
+
if arg.respond_to?(:collector)
|
|
9
|
+
!arg.collector.any?
|
|
10
|
+
elsif arg.respond_to?(:any?)
|
|
9
11
|
!arg.any? {|a| !is_empty?(a) }
|
|
10
12
|
elsif arg.respond_to?(:empty?)
|
|
11
13
|
arg.empty?
|
data/lib/stretchy/version.rb
CHANGED
data/lib/stretchy.rb
CHANGED
|
@@ -58,7 +58,7 @@ module Stretchy
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def index_document(params = {})
|
|
61
|
-
Utils.require_params!(:index_document, params, :index, :
|
|
61
|
+
Utils.require_params!(:index_document, params, :index, :body)
|
|
62
62
|
|
|
63
63
|
raise IndexDoesNotExistError.new(
|
|
64
64
|
"index #{params[:index]} does not exist"
|
data/solano.yml
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
ruby_version: ruby-2.
|
|
3
|
-
bundler_version: 1.
|
|
2
|
+
ruby_version: ruby-2.4.2
|
|
3
|
+
bundler_version: 1.14.6
|
|
4
4
|
test_pattern:
|
|
5
5
|
- spec/**/*_spec.rb
|
|
6
6
|
elasticsearch:
|
|
7
|
-
version:
|
|
7
|
+
version: 5.0.1
|
|
8
8
|
java:
|
|
9
|
-
java_version: java-
|
|
9
|
+
java_version: java-8-openjdk
|
data/stretchy.gemspec
CHANGED
|
@@ -19,14 +19,13 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
20
|
spec.require_paths = ["lib"]
|
|
21
21
|
|
|
22
|
-
spec.add_dependency "elasticsearch", "~>
|
|
22
|
+
spec.add_dependency "elasticsearch", "~> 7.0"
|
|
23
23
|
spec.add_dependency "excon", "~> 0.45"
|
|
24
24
|
|
|
25
|
-
spec.add_development_dependency "
|
|
26
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
|
25
|
+
spec.add_development_dependency "rake", "~> 10.4"
|
|
27
26
|
spec.add_development_dependency "rspec", "~> 3.2"
|
|
28
27
|
spec.add_development_dependency "fuubar", "~> 2.0"
|
|
29
28
|
spec.add_development_dependency "pry", "~> 0.10"
|
|
30
29
|
spec.add_development_dependency "awesome_print", "~> 1.6"
|
|
31
|
-
spec.add_development_dependency "yard", "~> 0.
|
|
30
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
|
32
31
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stretchy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- agius
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-08-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: elasticsearch
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '7.0'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '7.0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: excon
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,34 +38,20 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0.45'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: bundler
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '1.8'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '1.8'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
42
|
name: rake
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
58
44
|
requirements:
|
|
59
45
|
- - "~>"
|
|
60
46
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '10.
|
|
47
|
+
version: '10.4'
|
|
62
48
|
type: :development
|
|
63
49
|
prerelease: false
|
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
51
|
requirements:
|
|
66
52
|
- - "~>"
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '10.
|
|
54
|
+
version: '10.4'
|
|
69
55
|
- !ruby/object:Gem::Dependency
|
|
70
56
|
name: rspec
|
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -128,14 +114,14 @@ dependencies:
|
|
|
128
114
|
requirements:
|
|
129
115
|
- - "~>"
|
|
130
116
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0.
|
|
117
|
+
version: '0.9'
|
|
132
118
|
type: :development
|
|
133
119
|
prerelease: false
|
|
134
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
121
|
requirements:
|
|
136
122
|
- - "~>"
|
|
137
123
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0.
|
|
124
|
+
version: '0.9'
|
|
139
125
|
description: Build queries for Elasticsearch with a chainable interface like ActiveRecord's.
|
|
140
126
|
email:
|
|
141
127
|
- andrew@atevans.com
|
|
@@ -145,14 +131,18 @@ extra_rdoc_files: []
|
|
|
145
131
|
files:
|
|
146
132
|
- ".editorconfig"
|
|
147
133
|
- ".gitignore"
|
|
134
|
+
- ".rubocop.yml"
|
|
148
135
|
- ".ruby-version"
|
|
149
136
|
- ".yardopts"
|
|
137
|
+
- CHANGELOG.md
|
|
150
138
|
- CONTRIBUTING.md
|
|
139
|
+
- Dockerfile
|
|
151
140
|
- Gemfile
|
|
152
141
|
- README.md
|
|
153
142
|
- Rakefile
|
|
154
143
|
- bin/console
|
|
155
144
|
- bin/setup
|
|
145
|
+
- docker-compose.yml
|
|
156
146
|
- lib/stretchy.rb
|
|
157
147
|
- lib/stretchy/and_collector.rb
|
|
158
148
|
- lib/stretchy/api.rb
|
|
@@ -184,10 +174,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
184
174
|
- !ruby/object:Gem::Version
|
|
185
175
|
version: '0'
|
|
186
176
|
requirements: []
|
|
187
|
-
|
|
188
|
-
rubygems_version: 2.4.5
|
|
177
|
+
rubygems_version: 3.0.3
|
|
189
178
|
signing_key:
|
|
190
179
|
specification_version: 4
|
|
191
180
|
summary: Query builder for Elasticsearch
|
|
192
181
|
test_files: []
|
|
193
|
-
has_rdoc:
|