grn_mini 0.1.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.
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +368 -0
- data/Rakefile +8 -0
- data/grn_mini.gemspec +26 -0
- data/lib/grn_mini.rb +7 -0
- data/lib/grn_mini/array.rb +101 -0
- data/lib/grn_mini/util.rb +19 -0
- data/lib/grn_mini/version.rb +3 -0
- data/test/minitest_helper.rb +4 -0
- data/test/test_grn_mini.rb +7 -0
- data/test/test_grn_mini_array.rb +377 -0
- metadata +107 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 ongaeshi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,368 @@
|
|
1
|
+
# GrnMini
|
2
|
+
|
3
|
+
Groonga(Rroonga) wrapper for using easily. It is the KVS so easy to use.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
$ gem install grn_mini
|
8
|
+
|
9
|
+
## Basic Usage
|
10
|
+
|
11
|
+
### Create a database with the name "test.db".
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'grn_mini'
|
15
|
+
array = GrnMini::Array.new("test.db")
|
16
|
+
```
|
17
|
+
|
18
|
+
### Add the record with the number column and text column.
|
19
|
+
|
20
|
+
Determine the column type when you first call "GrnMini::Array#add". (Add inverted index if data type is "string".)
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
array.add(text: "aaa", number: 1)
|
24
|
+
```
|
25
|
+
|
26
|
+
### It is also possible to use the '<<'
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
array << {text: "bbb", number: 2}
|
30
|
+
array << {text: "ccc", number: 3}
|
31
|
+
array.size #=> 3
|
32
|
+
```
|
33
|
+
|
34
|
+
### Create a temporary database. (Useful for testing)
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
GrnMini::Array.tmpdb do |array|
|
38
|
+
array << {text: "aaa", number: 1}
|
39
|
+
array << {text: "bbb", number: 2}
|
40
|
+
array << {text: "ccc", number: 3}
|
41
|
+
end
|
42
|
+
# Delete temporary database
|
43
|
+
```
|
44
|
+
|
45
|
+
## Data Type
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
GrnMini::Array.tmpdb do |array|
|
49
|
+
array << {filename: "a.txt", int: 1, float: 1.5, time: Time.at(1999)}
|
50
|
+
array << {filename: "b.doc", int: 2, float: 2.5, time: Time.at(2000)}
|
51
|
+
|
52
|
+
# ShortText
|
53
|
+
array[1].filename #=> "a.txt"
|
54
|
+
array[2].filename #=> "b.doc"
|
55
|
+
|
56
|
+
# Int32
|
57
|
+
array[1].int #=> 1
|
58
|
+
array[2].int #=> 2
|
59
|
+
|
60
|
+
# Float
|
61
|
+
array[1].float #=> 1.5
|
62
|
+
array[2].float #=> 2.5
|
63
|
+
|
64
|
+
# Time
|
65
|
+
array[1].time #=> 1999-01-01
|
66
|
+
array[2].time #=> 2000-01-01
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
See also [8.4. Data type — Groonga documentation](http://groonga.org/docs/reference/types.html).
|
71
|
+
|
72
|
+
## Access Record
|
73
|
+
|
74
|
+
### Add
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'grn_mini'
|
78
|
+
|
79
|
+
array = GrnMini::Array.new("test2.db")
|
80
|
+
array << {name:"Tanaka", age: 11, height: 162.5}
|
81
|
+
array << {name:"Suzuki", age: 31, height: 170.0}
|
82
|
+
```
|
83
|
+
|
84
|
+
### Read
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
record = array[1] # Read from id (> 0)
|
88
|
+
record.id #=> 1
|
89
|
+
```
|
90
|
+
|
91
|
+
Access function with the same name as the column name
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
record.name #=> "Tanaka
|
95
|
+
record.age #=> 11
|
96
|
+
record.height #=> 162.5
|
97
|
+
```
|
98
|
+
|
99
|
+
Groonga::Record#attributes is useful for debug
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
record.attributes #=> {"_id"=>1, "age"=>11, "height"=>162.5, "name"=>"Tanaka"}
|
103
|
+
```
|
104
|
+
|
105
|
+
### Update
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
array[2].name = "Hayashi"
|
109
|
+
array[2].attributes #=> {"_id"=>2, "age"=>31, "height"=>170.0, "name"=>"Hayashi"}
|
110
|
+
```
|
111
|
+
|
112
|
+
### Delete
|
113
|
+
|
114
|
+
Delete by passing id.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
array.delete(1)
|
118
|
+
|
119
|
+
# It returns 'nil' value when you access a deleted record
|
120
|
+
array[1].attributes #=> {"_id"=>1, "age"=>0, "height"=>0.0, "name"=>nil}
|
121
|
+
|
122
|
+
# Can't see deleted records if access from Enumerable
|
123
|
+
array.first.id #=> 2
|
124
|
+
array.first.attributes #=> {"_id"=>2, "age"=>31, "height"=>170.0, "name"=>"Hayashi"}
|
125
|
+
```
|
126
|
+
|
127
|
+
It is also possible to pass the block.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
GrnMini::Array.tmpdb do |array|
|
131
|
+
array << {name:"Tanaka", age: 11, height: 162.5}
|
132
|
+
array << {name:"Suzuki", age: 31, height: 170.0}
|
133
|
+
array << {name:"Hayashi", age: 20, height: 165.0}
|
134
|
+
|
135
|
+
array.delete do |record|
|
136
|
+
record.age <= 20
|
137
|
+
end
|
138
|
+
|
139
|
+
array.size #=> 1
|
140
|
+
array.first.attributes #=> {"_id"=>2, "age"=>31, "height"=>170.0, "name"=>"Suzuki"}
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
## Search
|
145
|
+
|
146
|
+
Use GrnMini::Array#select method.
|
147
|
+
`:text` column is set to the `:default_column` implicitly.
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
GrnMini::Array.tmpdb do |array|
|
151
|
+
array << {text:"aaa", number:1}
|
152
|
+
array << {text:"bbb", number:20}
|
153
|
+
array << {text:"bbb ccc", number:2}
|
154
|
+
array << {text:"bbb", number:15}
|
155
|
+
array << {text:"ccc", number:3}
|
156
|
+
|
157
|
+
results = array.select("aaa")
|
158
|
+
results.map {|record| record.attributes} #=> [{"_id"=>1, "_key"=>{"_id"=>1, "number"=>1, "text"=>"aaa"}, "_score"=>1}]
|
159
|
+
|
160
|
+
# AND
|
161
|
+
results = array.select("bbb ccc")
|
162
|
+
results.map {|record| record.attributes} #=> [{"_id"=>2, "_key"=>{"_id"=>3, "number"=>2, "text"=>"bbb ccc"}, "_score"=>2}]
|
163
|
+
|
164
|
+
# Specify column
|
165
|
+
results = array.select("bbb number:<10")
|
166
|
+
results.map {|record| record.attributes} #=> [{"_id"=>2, "_key"=>{"_id"=>3, "number"=>2, "text"=>"bbb ccc"}, "_score"=>2}]
|
167
|
+
|
168
|
+
# AND, OR, Grouping
|
169
|
+
results = array.select("bbb (number:<= 10 OR number:>=20)")
|
170
|
+
results.map {|record| record.attributes} #=> [{"_id"=>2, "_key"=>{"_id"=>3, "number"=>2, "text"=>"bbb ccc"}, "_score"=>2}, {"_id"=>4, "_key"=>{"_id"=>2, "number"=>20, "text"=>"bbb"}, "_score"=>2}]
|
171
|
+
|
172
|
+
# NOT
|
173
|
+
results = array.select("bbb - ccc")
|
174
|
+
results.map {|record| record.attributes} #=> [{"_id"=>1, "_key"=>{"_id"=>2, "number"=>20, "text"=>"bbb"}, "_score"=>1}, {"_id"=>3, "_key"=>{"_id"=>4, "number"=>15, "text"=>"bbb"}, "_score"=>1}]
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
Change `:default_column` to `:filename` column.
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
GrnMini::Array.tmpdb do |array|
|
182
|
+
array << {text: "txt", filename:"a.txt"}
|
183
|
+
array << {text: "txt", filename:"a.doc"}
|
184
|
+
array << {text: "txt", filename:"a.rb"}
|
185
|
+
|
186
|
+
# Specify column
|
187
|
+
results = array.select("filename:@txt")
|
188
|
+
results.first.attributes #=> {"_id"=>1, "_key"=>{"_id"=>1, "filename"=>"a.txt", "text"=>"txt"}, "_score"=>1}
|
189
|
+
|
190
|
+
# Change default_column
|
191
|
+
results = array.select("txt", default_column: "filename")
|
192
|
+
results.first.attributes #=> {"_id"=>1, "_key"=>{"_id"=>1, "filename"=>"a.txt", "text"=>"txt"}, "_score"=>1}
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
See also [8.10.1. Query syntax](http://groonga.org/docs/reference/grn_expr/query_syntax.html), [Groonga::Table#select](http://ranguba.org/rroonga/en/Groonga/Table.html#select-instance_method)
|
197
|
+
|
198
|
+
## Sort
|
199
|
+
|
200
|
+
Specify column name to sort.
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
GrnMini::Array.tmpdb do |array|
|
204
|
+
array << {name:"Tanaka", age: 11, height: 162.5}
|
205
|
+
array << {name:"Suzuki", age: 31, height: 170.0}
|
206
|
+
array << {name:"Hayashi", age: 21, height: 175.4}
|
207
|
+
array << {name:"Suzuki", age: 5, height: 110.0}
|
208
|
+
|
209
|
+
sorted = array.sort(["age"])
|
210
|
+
|
211
|
+
sorted.map {|r| {name: r.name, age: r.age}}
|
212
|
+
#=> [{:name=>"Suzuki", :age=> 5},
|
213
|
+
# {:name=>"Tanaka", :age=>11},
|
214
|
+
# {:name=>"Hayashi", :age=>21},
|
215
|
+
# {:name=>"Suzuki", :age=>31}]
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
Combination sort.
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
sorted = array.sort([{key: "name", order: :ascending},
|
223
|
+
{key: "age" , order: :descending}])
|
224
|
+
|
225
|
+
sorted.map {|r| {name: r.name, age: r.age}}
|
226
|
+
#=> [{:name=>"Hayashi", :age=>21},
|
227
|
+
# {:name=>"Suzuki", :age=>31},
|
228
|
+
# {:name=>"Suzuki", :age=> 5},
|
229
|
+
# {:name=>"Tanaka", :age=>11}]
|
230
|
+
```
|
231
|
+
|
232
|
+
## Grouping
|
233
|
+
|
234
|
+
Drill down aka.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
GrnMini::Array.tmpdb do |array|
|
238
|
+
array << {text:"aaaa.txt", suffix:"txt", type:1}
|
239
|
+
array << {text:"aaaa.doc", suffix:"doc", type:2}
|
240
|
+
array << {text:"aabb.txt", suffix:"txt", type:2}
|
241
|
+
|
242
|
+
groups = GrnMini::Util::group_with_sort(array, "suffix")
|
243
|
+
|
244
|
+
groups.size #=> 2
|
245
|
+
[groups[0].key, groups[0].n_sub_records] #=> ["txt", 2]
|
246
|
+
[groups[1].key, groups[1].n_sub_records] #=> ["doc", 1]
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
250
|
+
Grouping from selection results.
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
GrnMini::Array.tmpdb do |array|
|
254
|
+
array << {text:"aaaa", suffix:"txt"}
|
255
|
+
array << {text:"aaaa", suffix:"doc"}
|
256
|
+
array << {text:"aaaa", suffix:"txt"}
|
257
|
+
array << {text:"cccc", suffix:"txt"}
|
258
|
+
|
259
|
+
results = array.select("aa")
|
260
|
+
groups = GrnMini::Util::group_with_sort(results, "suffix")
|
261
|
+
|
262
|
+
groups.size #=> 2
|
263
|
+
[groups[0].key, groups[0].n_sub_records] #=> ["txt", 2]
|
264
|
+
[groups[1].key, groups[1].n_sub_records] #=> ["doc", 1]
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
## Snippet
|
269
|
+
|
270
|
+
Display of keyword surrounding text. It is often used in search engine.
|
271
|
+
Use `GrnMini::Util::text_snippet_from_selection_results`.
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
GrnMini::Array.tmpdb do |array|
|
275
|
+
array << {text: <<EOF, filename: "aaa.txt"}
|
276
|
+
[1] This is a pen pep pea pek pet.
|
277
|
+
------------------------------
|
278
|
+
------------------------------
|
279
|
+
------------------------------
|
280
|
+
------------------------------
|
281
|
+
[2] This is a pen pep pea pek pet.
|
282
|
+
------------------------------
|
283
|
+
------------------------------
|
284
|
+
------------------------------
|
285
|
+
------------------------------
|
286
|
+
EOF
|
287
|
+
|
288
|
+
results = array.select("This pen")
|
289
|
+
snippet = GrnMini::Util::text_snippet_from_selection_results(results)
|
290
|
+
|
291
|
+
record = results.first
|
292
|
+
segments = snippet.execute(record.text)
|
293
|
+
segments.size #=> 2
|
294
|
+
segments[0] #=> "[1] <<This>> is a <<pen>> pep pea pek pet.\n------------------------------\n------------------------------\n---"
|
295
|
+
segments[1] #=> "--------\n------------------------------\n[2] <<This>> is a <<pen>> pep pea pek pet.\n-------------------------"
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
`GrnMini::Util::html_snippet_from_selection_results` is HTML escaped.
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
GrnMini::Array.tmpdb do |array|
|
303
|
+
array << {text: <<EOF, filename: "aaa.txt"}
|
304
|
+
<html>
|
305
|
+
<div>This is a pen pep pea pek pet.</div>
|
306
|
+
</html>
|
307
|
+
EOF
|
308
|
+
|
309
|
+
results = array.select("This pen")
|
310
|
+
snippet = GrnMini::Util::html_snippet_from_selection_results(results, '<span class="strong">', '</span>') # Default value is '<strong>', '</strong>'
|
311
|
+
|
312
|
+
record = results.first
|
313
|
+
segments = snippet.execute(record.text)
|
314
|
+
segments.size #=> 1
|
315
|
+
segments.first #=> "<html>\n <div><span class=\"strong\">This</span> is a <span class=\"strong\">pen</span> pep pea pek pet.</div>\n</html>\n"
|
316
|
+
end
|
317
|
+
```
|
318
|
+
|
319
|
+
See also [Groonga::Expression#snippet](http://ranguba.org/rroonga/en/Groonga/Expression.html#snippet-instance_method)
|
320
|
+
|
321
|
+
## Pagination
|
322
|
+
|
323
|
+
#paginate is more convenient than #sort if you want a pagination.
|
324
|
+
|
325
|
+
```ruby
|
326
|
+
GrnMini::Array.tmpdb do |array|
|
327
|
+
array << {text: "aaaa", filename: "1.txt"}
|
328
|
+
array << {text: "aaaa aaaa", filename: "2a.txt"}
|
329
|
+
array << {text: "aaaa aaaa aaaa", filename: "3.txt"}
|
330
|
+
array << {text: "aaaa aaaa", filename: "2b.txt"}
|
331
|
+
array << {text: "aaaa aaaa", filename: "2c.txt"}
|
332
|
+
array << {text: "aaaa aaaa", filename: "2d.txt"}
|
333
|
+
array << {text: "aaaa aaaa", filename: "2e.txt"}
|
334
|
+
array << {text: "aaaa aaaa", filename: "2f.txt"}
|
335
|
+
|
336
|
+
results = array.select("aaaa")
|
337
|
+
|
338
|
+
# -- page1 --
|
339
|
+
page_entries = results.paginate([["_score", :desc]], :page => 1, :size => 5)
|
340
|
+
|
341
|
+
# Total number of record
|
342
|
+
page_entries.n_records #=> 8
|
343
|
+
|
344
|
+
# Page offset
|
345
|
+
page_entries.start_offset #=> 1
|
346
|
+
page_entries.end_offset #=> 5
|
347
|
+
|
348
|
+
# Page entries
|
349
|
+
page_entries.size #=> 5
|
350
|
+
|
351
|
+
# -- page2 --
|
352
|
+
page_entries = results.paginate([["_score", :desc]], :page => 2, :size => 5)
|
353
|
+
|
354
|
+
# Sample page content display
|
355
|
+
puts "#{page_entries.n_records} hit. (#{page_entries.start_offset} - #{page_entries.end_offset})"
|
356
|
+
page_entries.each do |record|
|
357
|
+
puts "#{record.filename}: #{record.text}"
|
358
|
+
end
|
359
|
+
|
360
|
+
#=> 8 hit. (6 - 8)
|
361
|
+
# 2b.txt: aaaa aaaa
|
362
|
+
# 2f.txt: aaaa aaaa
|
363
|
+
# 1.txt: aaaa
|
364
|
+
end
|
365
|
+
```
|
366
|
+
|
367
|
+
See also [Groonga::Table#pagenate](http://ranguba.org/rroonga/en/Groonga/Table.html#paginate-instance_method)
|
368
|
+
|
data/Rakefile
ADDED
data/grn_mini.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'grn_mini/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "grn_mini"
|
8
|
+
spec.version = GrnMini::VERSION
|
9
|
+
spec.authors = ["ongaeshi"]
|
10
|
+
spec.email = ["ongaeshi0621@gmail.com"]
|
11
|
+
spec.description = %q{Groonga(Rroonga) wrapper for using easily.}
|
12
|
+
spec.summary = %q{It is the KVS it's so easy to use.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "minitest"
|
24
|
+
|
25
|
+
spec.add_dependency "rroonga"
|
26
|
+
end
|
data/lib/grn_mini.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'grn_mini/util'
|
2
|
+
require 'groonga'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module GrnMini
|
6
|
+
class Array
|
7
|
+
attr_accessor :grn
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def self.tmpdb
|
11
|
+
Dir.mktmpdir do |dir|
|
12
|
+
# p dir
|
13
|
+
yield self.new(File.join(dir, "tmp.db"))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(path)
|
18
|
+
unless File.exist?(path)
|
19
|
+
Groonga::Database.create(path: path)
|
20
|
+
else
|
21
|
+
Groonga::Database.open(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
unless Groonga["Array"]
|
25
|
+
@grn = Groonga::Array.create(name: "Array", persistent: true)
|
26
|
+
@terms = Groonga::PatriciaTrie.create(name: "Terms", key_normalize: true, default_tokenizer: "TokenBigramSplitSymbolAlphaDigit")
|
27
|
+
else
|
28
|
+
@grn = Groonga["Array"]
|
29
|
+
@terms = Groonga["Terms"]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add(hash)
|
34
|
+
if @grn.empty?
|
35
|
+
hash.each do |key, value|
|
36
|
+
column = key.to_s
|
37
|
+
|
38
|
+
# @todo Need define_index_column ?
|
39
|
+
if value.is_a?(Time)
|
40
|
+
@grn.define_column(column, "Time")
|
41
|
+
elsif value.is_a?(Float)
|
42
|
+
@grn.define_column(column, "Float")
|
43
|
+
elsif value.is_a?(Numeric)
|
44
|
+
@grn.define_column(column, "Int32")
|
45
|
+
else
|
46
|
+
@grn.define_column(column, "ShortText")
|
47
|
+
@terms.define_index_column("array_#{column}", @grn, source: "Array.#{column}", with_position: true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@grn.add(hash)
|
53
|
+
end
|
54
|
+
|
55
|
+
alias << add
|
56
|
+
|
57
|
+
def select(query, options = {default_column: "text"})
|
58
|
+
@grn.select(query, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def size
|
62
|
+
@grn.size
|
63
|
+
end
|
64
|
+
|
65
|
+
alias length size
|
66
|
+
|
67
|
+
def empty?
|
68
|
+
size == 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def each
|
72
|
+
@grn.each do |record|
|
73
|
+
yield record
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class IdIsGreaterThanZero < RuntimeError; end
|
78
|
+
|
79
|
+
def [](id)
|
80
|
+
raise IdIsGreaterThanZero if id == 0
|
81
|
+
@grn[id]
|
82
|
+
end
|
83
|
+
|
84
|
+
def delete(id = nil, &block)
|
85
|
+
if block_given?
|
86
|
+
@grn.delete(&block)
|
87
|
+
else
|
88
|
+
raise IdIsGreaterThanZero if id == 0
|
89
|
+
@grn.delete(id)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def sort(keys, options = {})
|
94
|
+
@grn.sort(keys, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def group(key, options = {})
|
98
|
+
@grn.group(key, options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'groonga'
|
2
|
+
|
3
|
+
module GrnMini
|
4
|
+
module Util
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def group_with_sort(table, column)
|
8
|
+
table.group(column).sort_by {|record| record.n_sub_records }.reverse
|
9
|
+
end
|
10
|
+
|
11
|
+
def text_snippet_from_selection_results(table, open_tag = '<<', close_tag = ">>")
|
12
|
+
table.expression.snippet([[open_tag, close_tag]])
|
13
|
+
end
|
14
|
+
|
15
|
+
def html_snippet_from_selection_results(table, open_tag = '<strong>', close_tag = "</strong>")
|
16
|
+
table.expression.snippet([[open_tag, close_tag]], {html_escape: true})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,377 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
|
3
|
+
class TestGrnMiniArray < MiniTest::Unit::TestCase
|
4
|
+
def test_initialize
|
5
|
+
Dir.mktmpdir do |dir|
|
6
|
+
array = GrnMini::Array.new(File.join(dir, "test.db"))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_add
|
11
|
+
GrnMini::Array.tmpdb do |array|
|
12
|
+
array.add(text:"aaa", number:1)
|
13
|
+
assert_equal 1, array.size
|
14
|
+
|
15
|
+
# alias << add
|
16
|
+
array << {text:"bbb", number:2}
|
17
|
+
assert_equal 2, array.size
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_select
|
22
|
+
GrnMini::Array.tmpdb do |array|
|
23
|
+
array << {text:"aaa", number:1}
|
24
|
+
array << {text:"bbb", number:2}
|
25
|
+
array << {text:"ccc", number:3}
|
26
|
+
|
27
|
+
results = array.select("bb")
|
28
|
+
|
29
|
+
assert_equal 1, results.size
|
30
|
+
assert_equal "bbb", results.first.text
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_select2
|
35
|
+
GrnMini::Array.tmpdb do |array|
|
36
|
+
array << {text:"aaa", number:1}
|
37
|
+
array << {text:"bbb", number:2}
|
38
|
+
array << {text:"bbb", number:20}
|
39
|
+
array << {text:"ccc", number:3}
|
40
|
+
|
41
|
+
results = array.select("bb number:<10")
|
42
|
+
|
43
|
+
assert_equal 1, results.size
|
44
|
+
assert_equal "bbb", results.first.text
|
45
|
+
assert_equal 2, results.first.number
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_size
|
50
|
+
GrnMini::Array.tmpdb do |array|
|
51
|
+
assert_equal 0, array.size
|
52
|
+
assert_equal 0, array.length
|
53
|
+
|
54
|
+
array << {text:"aaa", number:1}
|
55
|
+
array << {text:"bbb", number:2}
|
56
|
+
assert_equal 2, array.size
|
57
|
+
assert_equal 2, array.length
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_empty?
|
62
|
+
GrnMini::Array.tmpdb do |array|
|
63
|
+
assert_equal true, array.empty?
|
64
|
+
|
65
|
+
array << {text:"aaa", number:1}
|
66
|
+
assert_equal false, array.empty?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_each
|
71
|
+
GrnMini::Array.tmpdb do |array|
|
72
|
+
array << {text:"aaa", number:1}
|
73
|
+
array << {text:"bbb", number:2}
|
74
|
+
array << {text:"ccc", number:3}
|
75
|
+
|
76
|
+
array.each_with_index do |v, index|
|
77
|
+
case index
|
78
|
+
when 0
|
79
|
+
assert_equal "aaa", v.text
|
80
|
+
assert_equal 1, v.number
|
81
|
+
when 1
|
82
|
+
assert_equal "bbb", v.text
|
83
|
+
assert_equal 2, v.number
|
84
|
+
when 2
|
85
|
+
assert_equal "ccc", v.text
|
86
|
+
assert_equal 3, v.number
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_read_by_id
|
93
|
+
GrnMini::Array.tmpdb do |array|
|
94
|
+
array << {text:"aaa", number:1}
|
95
|
+
array << {text:"bbb", number:2}
|
96
|
+
array << {text:"ccc", number:3}
|
97
|
+
|
98
|
+
# id > 0
|
99
|
+
assert_raises(GrnMini::Array::IdIsGreaterThanZero) { array[0] }
|
100
|
+
|
101
|
+
record = array[1]
|
102
|
+
assert_equal 1, record.id
|
103
|
+
|
104
|
+
assert_equal "aaa", array[1].text
|
105
|
+
assert_equal "bbb", array[2].text
|
106
|
+
assert_equal "ccc", array[3].text
|
107
|
+
|
108
|
+
assert_equal nil, array[4].text
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_write_by_id
|
113
|
+
GrnMini::Array.tmpdb do |array|
|
114
|
+
array << {text:"aaa", number:1}
|
115
|
+
array << {text:"bbb", number:2}
|
116
|
+
array << {text:"ccc", number:3}
|
117
|
+
|
118
|
+
assert_equal "bbb", array[2].text
|
119
|
+
assert_equal 2, array[2].number
|
120
|
+
|
121
|
+
array[2].text = "BBB"
|
122
|
+
array[2].number = 22
|
123
|
+
|
124
|
+
assert_equal "BBB", array[2].text
|
125
|
+
assert_equal 22, array[2].number
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_delete_by_id
|
130
|
+
GrnMini::Array.tmpdb do |array|
|
131
|
+
array << {text:"aaa", number:1}
|
132
|
+
array << {text:"bbb", number:2}
|
133
|
+
array << {text:"ccc", number:3}
|
134
|
+
|
135
|
+
assert_equal 3, array.size
|
136
|
+
|
137
|
+
array.delete(2)
|
138
|
+
|
139
|
+
assert_equal 2, array.size
|
140
|
+
|
141
|
+
# Deleted elements are not stuffed between
|
142
|
+
assert_equal nil, array[2].text
|
143
|
+
assert_equal 0, array[2].number
|
144
|
+
|
145
|
+
# Can be accessed properly if use GrnMini::Array#each
|
146
|
+
array.each_with_index do |v, index|
|
147
|
+
case index
|
148
|
+
when 0
|
149
|
+
assert_equal "aaa", v.text
|
150
|
+
assert_equal 1, v.number
|
151
|
+
when 1
|
152
|
+
assert_equal "ccc", v.text
|
153
|
+
assert_equal 3, v.number
|
154
|
+
end
|
155
|
+
# p v.attributes
|
156
|
+
end
|
157
|
+
|
158
|
+
# New member id is '4'
|
159
|
+
array << {text:"ddd", number:1}
|
160
|
+
assert_equal "ddd", array[4].text
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_delete_by_block
|
165
|
+
GrnMini::Array.tmpdb do |array|
|
166
|
+
array << {text:"bbb", number:2}
|
167
|
+
array << {text:"ccc", number:3}
|
168
|
+
array << {text:"aaa", number:1}
|
169
|
+
|
170
|
+
array.delete do |record|
|
171
|
+
record.number >= 2
|
172
|
+
end
|
173
|
+
|
174
|
+
assert_equal 1, array.size
|
175
|
+
assert_equal "aaa", array.first.text
|
176
|
+
assert_equal 1, array.first.number
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_float_column
|
181
|
+
GrnMini::Array.tmpdb do |array|
|
182
|
+
array << {text:"aaaa", float: 1.5}
|
183
|
+
array << {text:"bbbb", float: 2.5}
|
184
|
+
array << {text:"cccc", float: 3.5}
|
185
|
+
|
186
|
+
assert_equal 2.5, array[2].float
|
187
|
+
|
188
|
+
results = array.select("float:>2.6")
|
189
|
+
assert_equal 3.5, results.first.float
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_time_column
|
194
|
+
GrnMini::Array.tmpdb do |array|
|
195
|
+
array << {text:"aaaa", timestamp: Time.new(2013)} # 2013-01-01
|
196
|
+
array << {text:"bbbb", timestamp: Time.new(2014)} # 2014-01-01
|
197
|
+
array << {text:"cccc", timestamp: Time.new(2015)} # 2015-01-01
|
198
|
+
|
199
|
+
assert_equal Time.new(2014), array[2].timestamp
|
200
|
+
|
201
|
+
results = array.select("timestamp:<=#{Time.new(2013,12).to_i}")
|
202
|
+
assert_equal 1, results.size
|
203
|
+
assert_equal Time.new(2013), results.first.timestamp
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_record_attributes
|
208
|
+
GrnMini::Array.tmpdb do |array|
|
209
|
+
array << {text:"aaaa", int: 1}
|
210
|
+
array << {text:"bbbb", int: 2}
|
211
|
+
array << {text:"cccc", int: 3}
|
212
|
+
|
213
|
+
assert_equal({"_id"=>1, "int"=>1, "text"=>"aaaa"}, array[1].attributes)
|
214
|
+
assert_equal({"_id"=>2, "int"=>2, "text"=>"bbbb"}, array[2].attributes)
|
215
|
+
assert_equal({"_id"=>3, "int"=>3, "text"=>"cccc"}, array[3].attributes)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_assign_long_text_to_short_text
|
220
|
+
GrnMini::Array.tmpdb do |array|
|
221
|
+
array << {filename:"a.txt"}
|
222
|
+
array << {filename:"a"*4095 + ".txt" } # Over 4095 byte (ShortText limit)
|
223
|
+
|
224
|
+
results = array.select("txt", default_column: "filename")
|
225
|
+
assert_equal 2, results.size
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_grn_object
|
230
|
+
GrnMini::Array.tmpdb do |array|
|
231
|
+
array << {text: "aaaa", filename:"a.txt", int: 1, float: 1.5, time: Time.at(2001)}
|
232
|
+
|
233
|
+
raw = array.grn
|
234
|
+
|
235
|
+
assert_equal true, raw.have_column?("filename")
|
236
|
+
assert_equal true, raw.have_column?("int")
|
237
|
+
assert_equal true, raw.have_column?("float")
|
238
|
+
assert_equal true, raw.have_column?("time")
|
239
|
+
assert_equal false, raw.have_column?("timeee")
|
240
|
+
|
241
|
+
assert_equal "ShortText", raw.column("text").range.name
|
242
|
+
assert_equal "ShortText", raw.column("filename").range.name
|
243
|
+
assert_equal "Int32" , raw.column("int").range.name
|
244
|
+
assert_equal "Float" , raw.column("float").range.name
|
245
|
+
assert_equal "Time" , raw.column("time").range.name
|
246
|
+
|
247
|
+
assert_equal false, raw.support_key?
|
248
|
+
assert_equal false, raw.support_sub_records?
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def test_sort
|
253
|
+
GrnMini::Array.tmpdb do |array|
|
254
|
+
array << {name:"Tanaka", age: 11, height: 162.5}
|
255
|
+
array << {name:"Suzuki", age: 31, height: 170.0}
|
256
|
+
array << {name:"Hayashi", age: 21, height: 175.4}
|
257
|
+
array << {name:"Suzuki", age: 5, height: 110.0}
|
258
|
+
|
259
|
+
sorted_by_age = array.sort(["age"])
|
260
|
+
sorted_array = sorted_by_age.map {|r| {name: r.name, age: r.age}}
|
261
|
+
assert_equal [{:name=>"Suzuki", :age=>5},
|
262
|
+
{:name=>"Tanaka", :age=>11},
|
263
|
+
{:name=>"Hayashi", :age=>21},
|
264
|
+
{:name=>"Suzuki", :age=>31}], sorted_array
|
265
|
+
|
266
|
+
sorted_by_combination = array.sort([{key: "name", order: :ascending},
|
267
|
+
{key: "age" , order: :descending}])
|
268
|
+
sorted_array = sorted_by_combination.map {|r| {name: r.name, age: r.age}}
|
269
|
+
assert_equal [{:name=>"Hayashi", :age=>21},
|
270
|
+
{:name=>"Suzuki", :age=>31},
|
271
|
+
{:name=>"Suzuki", :age=>5},
|
272
|
+
{:name=>"Tanaka", :age=>11}], sorted_array
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_group_from_array
|
277
|
+
GrnMini::Array.tmpdb do |array|
|
278
|
+
array << {text:"aaaa.txt", suffix:"txt", type:1}
|
279
|
+
array << {text:"aaaa.doc", suffix:"doc", type:2}
|
280
|
+
array << {text:"aabb.txt", suffix:"txt", type:2}
|
281
|
+
|
282
|
+
groups = GrnMini::Util::group_with_sort(array, "suffix")
|
283
|
+
|
284
|
+
assert_equal 2, groups.size
|
285
|
+
assert_equal ["txt", 2], [groups[0].key, groups[0].n_sub_records]
|
286
|
+
assert_equal ["doc", 1], [groups[1].key, groups[1].n_sub_records]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_group_from_selection_results
|
291
|
+
GrnMini::Array.tmpdb do |array|
|
292
|
+
array << {text:"aaaa", suffix:"txt"}
|
293
|
+
array << {text:"aaaa", suffix:"doc"}
|
294
|
+
array << {text:"aaaa", suffix:"txt"}
|
295
|
+
array << {text:"cccc", suffix:"txt"}
|
296
|
+
|
297
|
+
results = array.select("aa")
|
298
|
+
groups = GrnMini::Util::group_with_sort(results, "suffix")
|
299
|
+
|
300
|
+
assert_equal 2, groups.size
|
301
|
+
assert_equal ["txt", 2], [groups[0].key, groups[0].n_sub_records]
|
302
|
+
assert_equal ["doc", 1], [groups[1].key, groups[1].n_sub_records]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_text_snippet_from_selection_results
|
307
|
+
GrnMini::Array.tmpdb do |array|
|
308
|
+
array << {text: <<EOF, filename: "aaa.txt"}
|
309
|
+
[1] This is a pen pep pea pek pet.
|
310
|
+
------------------------------
|
311
|
+
------------------------------
|
312
|
+
------------------------------
|
313
|
+
------------------------------
|
314
|
+
[2] This is a pen pep pea pek pet.
|
315
|
+
------------------------------
|
316
|
+
------------------------------
|
317
|
+
------------------------------
|
318
|
+
------------------------------
|
319
|
+
------------------------------
|
320
|
+
EOF
|
321
|
+
|
322
|
+
results = array.select("This pen")
|
323
|
+
snippet = GrnMini::Util::text_snippet_from_selection_results(results)
|
324
|
+
|
325
|
+
record = results.first
|
326
|
+
segments = snippet.execute(record.text)
|
327
|
+
assert_equal 2, segments.size
|
328
|
+
assert_match /\[1\] <<This>> is a <<pen>> pep pea pek pet./, segments[0]
|
329
|
+
assert_match /\[2\] <<This>> is a <<pen>> pep pea pek pet./, segments[1]
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_html_snippet_from_selection_results
|
334
|
+
GrnMini::Array.tmpdb do |array|
|
335
|
+
array << {text: <<EOF, filename: "aaa.txt"}
|
336
|
+
<html>
|
337
|
+
<div>This is a pen pep pea pek pet.</div>
|
338
|
+
</html>
|
339
|
+
EOF
|
340
|
+
|
341
|
+
results = array.select("This pen")
|
342
|
+
snippet = GrnMini::Util::html_snippet_from_selection_results(results)
|
343
|
+
|
344
|
+
record = results.first
|
345
|
+
segments = snippet.execute(record.text)
|
346
|
+
assert_equal 1, segments.size
|
347
|
+
assert_equal "<html>\n <div><strong>This</strong> is a <strong>pen</strong> pep pea pek pet.</div>\n</html>\n", segments.first
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def test_paginate
|
352
|
+
GrnMini::Array.tmpdb do |array|
|
353
|
+
array << {text: "aaaa", filename: "1.txt"}
|
354
|
+
array << {text: "aaaa aaaa", filename: "2a.txt"}
|
355
|
+
array << {text: "aaaa aaaa aaaa", filename: "3.txt"}
|
356
|
+
array << {text: "aaaa aaaa", filename: "2b.txt"}
|
357
|
+
array << {text: "aaaa aaaa", filename: "2c.txt"}
|
358
|
+
array << {text: "aaaa aaaa", filename: "2d.txt"}
|
359
|
+
|
360
|
+
results = array.select("aaaa")
|
361
|
+
|
362
|
+
# page1
|
363
|
+
page_entries = results.paginate([["_score", :desc]], :page => 1, :size => 5)
|
364
|
+
assert_equal 6, page_entries.n_records
|
365
|
+
assert_equal 1, page_entries.start_offset
|
366
|
+
assert_equal 5, page_entries.end_offset
|
367
|
+
assert_equal "3.txt", page_entries.first.filename
|
368
|
+
|
369
|
+
# page2
|
370
|
+
page_entries = results.paginate([["_score", :desc]], :page => 2, :size => 5)
|
371
|
+
assert_equal 6, page_entries.n_records
|
372
|
+
assert_equal 6, page_entries.start_offset
|
373
|
+
assert_equal 6, page_entries.end_offset
|
374
|
+
assert_equal "1.txt", page_entries.first.filename
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grn_mini
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- ongaeshi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-05 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &2156695780 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *2156695780
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &2156695360 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *2156695360
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &2156694900 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *2156694900
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rroonga
|
49
|
+
requirement: &2156694480 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *2156694480
|
58
|
+
description: Groonga(Rroonga) wrapper for using easily.
|
59
|
+
email:
|
60
|
+
- ongaeshi0621@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .travis.yml
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- grn_mini.gemspec
|
72
|
+
- lib/grn_mini.rb
|
73
|
+
- lib/grn_mini/array.rb
|
74
|
+
- lib/grn_mini/util.rb
|
75
|
+
- lib/grn_mini/version.rb
|
76
|
+
- test/minitest_helper.rb
|
77
|
+
- test/test_grn_mini.rb
|
78
|
+
- test/test_grn_mini_array.rb
|
79
|
+
homepage: ''
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.8.6
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: It is the KVS it's so easy to use.
|
104
|
+
test_files:
|
105
|
+
- test/minitest_helper.rb
|
106
|
+
- test/test_grn_mini.rb
|
107
|
+
- test/test_grn_mini_array.rb
|