redis 3.0.5 → 3.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +9 -8
  3. data/.travis.yml +10 -8
  4. data/CHANGELOG.md +4 -0
  5. data/Rakefile +21 -10
  6. data/lib/redis.rb +229 -72
  7. data/lib/redis/version.rb +1 -1
  8. data/test/blocking_commands_test.rb +1 -1
  9. data/test/command_map_test.rb +1 -1
  10. data/test/commands_on_hashes_test.rb +1 -1
  11. data/test/commands_on_lists_test.rb +1 -1
  12. data/test/commands_on_sets_test.rb +1 -1
  13. data/test/commands_on_sorted_sets_test.rb +1 -1
  14. data/test/commands_on_strings_test.rb +14 -14
  15. data/test/commands_on_value_types_test.rb +1 -1
  16. data/test/connection_handling_test.rb +1 -1
  17. data/test/distributed_blocking_commands_test.rb +1 -1
  18. data/test/distributed_commands_on_hashes_test.rb +1 -1
  19. data/test/distributed_commands_on_lists_test.rb +1 -1
  20. data/test/distributed_commands_on_sets_test.rb +1 -1
  21. data/test/distributed_commands_on_sorted_sets_test.rb +1 -1
  22. data/test/distributed_commands_on_strings_test.rb +7 -7
  23. data/test/distributed_commands_on_value_types_test.rb +1 -1
  24. data/test/distributed_commands_requiring_clustering_test.rb +14 -14
  25. data/test/distributed_connection_handling_test.rb +1 -1
  26. data/test/distributed_internals_test.rb +1 -1
  27. data/test/distributed_key_tags_test.rb +1 -1
  28. data/test/distributed_persistence_control_commands_test.rb +1 -1
  29. data/test/distributed_publish_subscribe_test.rb +1 -1
  30. data/test/distributed_remote_server_control_commands_test.rb +15 -15
  31. data/test/distributed_scripting_test.rb +57 -57
  32. data/test/distributed_sorting_test.rb +1 -1
  33. data/test/distributed_test.rb +1 -1
  34. data/test/distributed_transactions_test.rb +1 -1
  35. data/test/encoding_test.rb +1 -1
  36. data/test/error_replies_test.rb +1 -1
  37. data/test/helper.rb +10 -1
  38. data/test/helper_test.rb +1 -1
  39. data/test/internals_test.rb +20 -9
  40. data/test/lint/hashes.rb +17 -17
  41. data/test/lint/lists.rb +10 -10
  42. data/test/lint/sets.rb +14 -14
  43. data/test/lint/sorted_sets.rb +30 -30
  44. data/test/lint/strings.rb +45 -45
  45. data/test/lint/value_types.rb +32 -32
  46. data/test/persistence_control_commands_test.rb +1 -1
  47. data/test/pipelining_commands_test.rb +1 -1
  48. data/test/publish_subscribe_test.rb +1 -1
  49. data/test/remote_server_control_commands_test.rb +7 -7
  50. data/test/scanning_test.rb +413 -0
  51. data/test/scripting_test.rb +45 -45
  52. data/test/sorting_test.rb +1 -1
  53. data/test/thread_safety_test.rb +1 -1
  54. data/test/transactions_test.rb +1 -1
  55. data/test/unknown_commands_test.rb +1 -1
  56. data/test/url_param_test.rb +1 -1
  57. metadata +6 -4
  58. data/test/db/.gitignore +0 -1
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OWE3ZTkzYTU5MjZkMWY5YmQ0YTM0NmQ1NWY2ZTVmZTljNmZmYzE1Yg==
4
+ MWU4NjJlZTMzODJmNjI0MTk5ZWQ4Yzk1OGIxNGUwYmQzNzcwZGM0Zg==
5
5
  data.tar.gz: !binary |-
6
- OTY0NWU0ZTA3YzdiNTdhOTdiMmQzMzk2NWQ1ZjBkN2M2N2MxMGEzYg==
6
+ ZmQzMjMxMmU2NWRhY2E3NGE1N2I1OWExMjQxMzQ2YmY1MzcxOTM2Mw==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZDA2NjI0ZWE0YjM2NDc4MmE0MTZmODIyN2Q5MDgxMDc1NGFiODhiM2M1NGEy
10
- MGI1ODA2NzhmMDQ3OGY5ZGJhNzM3MzI0MjMyZGFkM2FjNDM3YzdlNTlkMjFh
11
- OTM0ZjQyNTc5OGMwOTkwNGQxZWYyY2E3ODk5Mzc0YmY1ODEzOWE=
9
+ NTZjYTMwZjU5ZWMwZDI0ZWM4OTZmZGU3YmMzMGJlZmZjMDUxNjUwMzNhZGMy
10
+ OTZkOWJmNTM5NTQzMWUyZTRjZmMzNWQ5MDY1NDljNzJiNWMzMGE4NGZjMDI0
11
+ YzM4YTE5MGM5MWVmN2YyYTI3YTViMzljMDg2YzQyYTZlY2M0OTE=
12
12
  data.tar.gz: !binary |-
13
- MGYyMzA5MDg1YTU1ZTcyYjVkMDljZjU4OGFhMDc1MTg3NzhhMjkzZDQyZjk5
14
- MTVmZjJhNmRiZDc0NjE3ODgzMTE5NTMyZGM3YjM0ZjMzMzkyYjA3MjNmYWYx
15
- MzZjNTk2NzRiOGMzYmYyODZkYzZjZjE5YWU0OTc2MWUwMTAyODk=
13
+ Mzk0NmVkMmEwYjc1MzIzOTU4MDEwN2QwNmRhZThkMzA2NzIyOTU5YjlmMzMz
14
+ Y2NkZjUyZTE5YmI2MjFiZTA4YjNkOWMzZWEwYWRkMjZlNDgzNTc3NDViOTRl
15
+ ZDMyZDlhYThhZjMxOGM5YzE1NjNkZDQzNjAwODEyNjM3ZmQ3OWE=
data/.gitignore CHANGED
@@ -1,10 +1,11 @@
1
- nohup.out
2
- redis/*
3
- rdsrv
4
- pkg/*
5
- coverage/*
6
- .idea
7
1
  *.rdb
8
2
  *.swp
9
- .yardoc
10
- doc/
3
+ /.idea
4
+ /.yardoc
5
+ /coverage/*
6
+ /doc/
7
+ /nohup.out
8
+ /pkg/*
9
+ /rdsrv
10
+ /redis/*
11
+ /test/db
@@ -16,30 +16,32 @@ gemfile:
16
16
  - .travis/Gemfile
17
17
 
18
18
  env:
19
- - conn=ruby
20
- - conn=hiredis
21
- - conn=synchrony
19
+ - conn=ruby REDIS_BRANCH=2.6
20
+ - conn=hiredis REDIS_BRANCH=2.6
21
+ - conn=synchrony REDIS_BRANCH=2.6
22
+ - conn=ruby REDIS_BRANCH=2.8
23
+ - conn=ruby REDIS_BRANCH=unstable
22
24
 
23
25
  matrix:
24
26
  exclude:
25
27
  # hiredis
26
28
  - rvm: jruby-18mode
27
29
  gemfile: .travis/Gemfile
28
- env: conn=hiredis
30
+ env: conn=hiredis REDIS_BRANCH=2.6
29
31
  - rvm: jruby-19mode
30
32
  gemfile: .travis/Gemfile
31
- env: conn=hiredis
33
+ env: conn=hiredis REDIS_BRANCH=2.6
32
34
 
33
35
  # synchrony
34
36
  - rvm: 1.8.7
35
37
  gemfile: .travis/Gemfile
36
- env: conn=synchrony
38
+ env: conn=synchrony REDIS_BRANCH=2.6
37
39
  - rvm: jruby-18mode
38
40
  gemfile: .travis/Gemfile
39
- env: conn=synchrony
41
+ env: conn=synchrony REDIS_BRANCH=2.6
40
42
  - rvm: jruby-19mode
41
43
  gemfile: .travis/Gemfile
42
- env: conn=synchrony
44
+ env: conn=synchrony REDIS_BRANCH=2.6
43
45
 
44
46
  notifications:
45
47
  irc:
@@ -1,3 +1,7 @@
1
+ # 3.0.6
2
+
3
+ * Added support for `SCAN` and variants.
4
+
1
5
  # 3.0.5
2
6
 
3
7
  * Fix calling #select from a pipeline (#309).
data/Rakefile CHANGED
@@ -2,12 +2,15 @@ require 'rubygems'
2
2
  require 'rubygems/package_task'
3
3
  require 'rake/testtask'
4
4
 
5
+ ENV["REDIS_BRANCH"] ||= "unstable"
6
+
5
7
  $:.unshift File.join(File.dirname(__FILE__), 'lib')
6
8
  require 'redis/version'
7
9
 
8
10
  REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
9
11
  REDIS_CNF = File.join(REDIS_DIR, "test.conf")
10
12
  REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
13
+ BINARY = "tmp/redis-#{ENV["REDIS_BRANCH"]}/src/redis-server"
11
14
 
12
15
  task :default => :run
13
16
 
@@ -15,7 +18,9 @@ desc "Run tests and manage server start/stop"
15
18
  task :run => [:start, :test, :stop]
16
19
 
17
20
  desc "Start the Redis server"
18
- task :start do
21
+ task :start => BINARY do
22
+ sh "#{BINARY} --version"
23
+
19
24
  redis_running = \
20
25
  begin
21
26
  File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
@@ -25,14 +30,8 @@ task :start do
25
30
  end
26
31
 
27
32
  unless redis_running
28
- unless system("which redis-server")
29
- STDERR.puts "redis-server not in PATH"
30
- exit 1
31
- end
32
-
33
- unless system("redis-server #{REDIS_CNF}")
34
- STDERR.puts "could not start redis-server"
35
- exit 1
33
+ unless system("#{BINARY} #{REDIS_CNF}")
34
+ abort "could not start redis-server"
36
35
  end
37
36
  end
38
37
  end
@@ -45,9 +44,21 @@ task :stop do
45
44
  end
46
45
  end
47
46
 
47
+ file BINARY do
48
+ branch = ENV.fetch("REDIS_BRANCH")
49
+
50
+ sh <<-SH
51
+ mkdir -p tmp;
52
+ cd tmp;
53
+ wget https://github.com/antirez/redis/archive/#{branch}.tar.gz -O #{branch}.tar.gz;
54
+ tar xf #{branch}.tar.gz;
55
+ cd redis-#{branch};
56
+ make
57
+ SH
58
+ end
59
+
48
60
  Rake::TestTask.new do |t|
49
61
  t.options = "-v"
50
- t.libs << "test"
51
62
  t.test_files = FileList["test/*_test.rb"]
52
63
  end
53
64
 
@@ -135,7 +135,7 @@ class Redis
135
135
  synchronize do |client|
136
136
  client.call([:config, action] + args) do |reply|
137
137
  if reply.kind_of?(Array) && action == :get
138
- Hash[reply.each_slice(2).to_a]
138
+ Hash[_pairify(reply)]
139
139
  else
140
140
  reply
141
141
  end
@@ -639,9 +639,7 @@ class Redis
639
639
  # @return [Float] value after incrementing it
640
640
  def incrbyfloat(key, increment)
641
641
  synchronize do |client|
642
- client.call([:incrbyfloat, key, increment]) do |reply|
643
- _floatify(reply) if reply
644
- end
642
+ client.call([:incrbyfloat, key, increment], &_floatify)
645
643
  end
646
644
  end
647
645
 
@@ -1417,9 +1415,7 @@ class Redis
1417
1415
  # @return [Float] score of the member after incrementing it
1418
1416
  def zincrby(key, increment, member)
1419
1417
  synchronize do |client|
1420
- client.call([:zincrby, key, increment, member]) do |reply|
1421
- _floatify(reply) if reply
1422
- end
1418
+ client.call([:zincrby, key, increment, member], &_floatify)
1423
1419
  end
1424
1420
  end
1425
1421
 
@@ -1465,9 +1461,7 @@ class Redis
1465
1461
  # @return [Float] score of the member
1466
1462
  def zscore(key, member)
1467
1463
  synchronize do |client|
1468
- client.call([:zscore, key, member]) do |reply|
1469
- _floatify(reply) if reply
1470
- end
1464
+ client.call([:zscore, key, member], &_floatify)
1471
1465
  end
1472
1466
  end
1473
1467
 
@@ -1493,20 +1487,14 @@ class Redis
1493
1487
  args = []
1494
1488
 
1495
1489
  with_scores = options[:with_scores] || options[:withscores]
1496
- args << "WITHSCORES" if with_scores
1490
+
1491
+ if with_scores
1492
+ args << "WITHSCORES"
1493
+ block = _floatify_pairs
1494
+ end
1497
1495
 
1498
1496
  synchronize do |client|
1499
- client.call([:zrange, key, start, stop] + args) do |reply|
1500
- if with_scores
1501
- if reply
1502
- reply.each_slice(2).map do |member, score|
1503
- [member, _floatify(score)]
1504
- end
1505
- end
1506
- else
1507
- reply
1508
- end
1509
- end
1497
+ client.call([:zrange, key, start, stop] + args, &block)
1510
1498
  end
1511
1499
  end
1512
1500
 
@@ -1525,20 +1513,14 @@ class Redis
1525
1513
  args = []
1526
1514
 
1527
1515
  with_scores = options[:with_scores] || options[:withscores]
1528
- args << "WITHSCORES" if with_scores
1516
+
1517
+ if with_scores
1518
+ args << "WITHSCORES"
1519
+ block = _floatify_pairs
1520
+ end
1529
1521
 
1530
1522
  synchronize do |client|
1531
- client.call([:zrevrange, key, start, stop] + args) do |reply|
1532
- if with_scores
1533
- if reply
1534
- reply.each_slice(2).map do |member, score|
1535
- [member, _floatify(score)]
1536
- end
1537
- end
1538
- else
1539
- reply
1540
- end
1541
- end
1523
+ client.call([:zrevrange, key, start, stop] + args, &block)
1542
1524
  end
1543
1525
  end
1544
1526
 
@@ -1615,23 +1597,17 @@ class Redis
1615
1597
  args = []
1616
1598
 
1617
1599
  with_scores = options[:with_scores] || options[:withscores]
1618
- args.concat(["WITHSCORES"]) if with_scores
1600
+
1601
+ if with_scores
1602
+ args << "WITHSCORES"
1603
+ block = _floatify_pairs
1604
+ end
1619
1605
 
1620
1606
  limit = options[:limit]
1621
1607
  args.concat(["LIMIT"] + limit) if limit
1622
1608
 
1623
1609
  synchronize do |client|
1624
- client.call([:zrangebyscore, key, min, max] + args) do |reply|
1625
- if with_scores
1626
- if reply
1627
- reply.each_slice(2).map do |member, score|
1628
- [member, _floatify(score)]
1629
- end
1630
- end
1631
- else
1632
- reply
1633
- end
1634
- end
1610
+ client.call([:zrangebyscore, key, min, max] + args, &block)
1635
1611
  end
1636
1612
  end
1637
1613
 
@@ -1653,23 +1629,17 @@ class Redis
1653
1629
  args = []
1654
1630
 
1655
1631
  with_scores = options[:with_scores] || options[:withscores]
1656
- args.concat(["WITHSCORES"]) if with_scores
1632
+
1633
+ if with_scores
1634
+ args << ["WITHSCORES"]
1635
+ block = _floatify_pairs
1636
+ end
1657
1637
 
1658
1638
  limit = options[:limit]
1659
1639
  args.concat(["LIMIT"] + limit) if limit
1660
1640
 
1661
1641
  synchronize do |client|
1662
- client.call([:zrevrangebyscore, key, max, min] + args) do |reply|
1663
- if with_scores
1664
- if reply
1665
- reply.each_slice(2).map do |member, score|
1666
- [member, _floatify(score)]
1667
- end
1668
- end
1669
- else
1670
- reply
1671
- end
1672
- end
1642
+ client.call([:zrevrangebyscore, key, max, min] + args, &block)
1673
1643
  end
1674
1644
  end
1675
1645
 
@@ -1931,9 +1901,7 @@ class Redis
1931
1901
  # @return [Float] value of the field after incrementing it
1932
1902
  def hincrbyfloat(key, field, increment)
1933
1903
  synchronize do |client|
1934
- client.call([:hincrbyfloat, key, field, increment]) do |reply|
1935
- _floatify(reply) if reply
1936
- end
1904
+ client.call([:hincrbyfloat, key, field, increment], &_floatify)
1937
1905
  end
1938
1906
  end
1939
1907
 
@@ -2271,18 +2239,189 @@ class Redis
2271
2239
  _eval(:evalsha, args)
2272
2240
  end
2273
2241
 
2274
- def id
2242
+ def _scan(command, cursor, args, options = {}, &block)
2243
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
2244
+
2245
+ args << cursor
2246
+
2247
+ if match = options[:match]
2248
+ args.concat(["MATCH", match])
2249
+ end
2250
+
2251
+ if count = options[:count]
2252
+ args.concat(["COUNT", count])
2253
+ end
2254
+
2275
2255
  synchronize do |client|
2276
- client.id
2256
+ client.call([command] + args, &block)
2277
2257
  end
2278
2258
  end
2279
2259
 
2280
- def inspect
2281
- synchronize do |client|
2282
- "#<Redis client v#{Redis::VERSION} for #{@original_client.id}>"
2260
+ # Scan the keyspace
2261
+ #
2262
+ # @example Retrieve the first batch of keys
2263
+ # redis.scan(0)
2264
+ # # => ["4", ["key:21", "key:47", "key:42"]]
2265
+ # @example Retrieve a batch of keys matching a pattern
2266
+ # redis.scan(4, :match => "key:1?")
2267
+ # # => ["92", ["key:13", "key:18"]]
2268
+ #
2269
+ # @param [String, Integer] cursor: the cursor of the iteration
2270
+ # @param [Hash] options
2271
+ # - `:match => String`: only return keys matching the pattern
2272
+ # - `:count => Integer`: return count keys at most per iteration
2273
+ #
2274
+ # @return [String, Array<String>] the next cursor and all found keys
2275
+ def scan(cursor, options={})
2276
+ _scan(:scan, cursor, [], options)
2277
+ end
2278
+
2279
+ # Scan the keyspace
2280
+ #
2281
+ # @example Retrieve all of the keys (with possible duplicates)
2282
+ # redis.scan_each.to_a
2283
+ # # => ["key:21", "key:47", "key:42"]
2284
+ # @example Execute block for each key matching a pattern
2285
+ # redis.scan_each(:match => "key:1?") {|key| puts key}
2286
+ # # => key:13
2287
+ # # => key:18
2288
+ #
2289
+ # @param [Hash] options
2290
+ # - `:match => String`: only return keys matching the pattern
2291
+ # - `:count => Integer`: return count keys at most per iteration
2292
+ #
2293
+ # @return [Enumerator] an enumerator for all found keys
2294
+ def scan_each(options={}, &block)
2295
+ return to_enum(:scan_each, options) unless block_given?
2296
+ cursor = 0
2297
+ loop do
2298
+ cursor, keys = scan(cursor, options)
2299
+ keys.each(&block)
2300
+ break if cursor == "0"
2301
+ end
2302
+ end
2303
+
2304
+ # Scan a hash
2305
+ #
2306
+ # @example Retrieve the first batch of key/value pairs in a hash
2307
+ # redis.hscan("hash", 0)
2308
+ #
2309
+ # @param [String, Integer] cursor: the cursor of the iteration
2310
+ # @param [Hash] options
2311
+ # - `:match => String`: only return keys matching the pattern
2312
+ # - `:count => Integer`: return count keys at most per iteration
2313
+ #
2314
+ # @return [String, Array<[String, String]>] the next cursor and all found keys
2315
+ def hscan(key, cursor, options={})
2316
+ _scan(:hscan, cursor, [key], options) do |reply|
2317
+ [reply[0], _pairify(reply[1])]
2318
+ end
2319
+ end
2320
+
2321
+ # Scan a hash
2322
+ #
2323
+ # @example Retrieve all of the key/value pairs in a hash
2324
+ # redis.hscan_each("hash").to_a
2325
+ # # => [["key70", "70"], ["key80", "80"]]
2326
+ #
2327
+ # @param [Hash] options
2328
+ # - `:match => String`: only return keys matching the pattern
2329
+ # - `:count => Integer`: return count keys at most per iteration
2330
+ #
2331
+ # @return [Enumerator] an enumerator for all found keys
2332
+ def hscan_each(key, options={}, &block)
2333
+ return to_enum(:hscan_each, key, options) unless block_given?
2334
+ cursor = 0
2335
+ loop do
2336
+ cursor, values = hscan(key, cursor, options)
2337
+ values.each(&block)
2338
+ break if cursor == "0"
2339
+ end
2340
+ end
2341
+
2342
+ # Scan a sorted set
2343
+ #
2344
+ # @example Retrieve the first batch of key/value pairs in a hash
2345
+ # redis.zscan("zset", 0)
2346
+ #
2347
+ # @param [String, Integer] cursor: the cursor of the iteration
2348
+ # @param [Hash] options
2349
+ # - `:match => String`: only return keys matching the pattern
2350
+ # - `:count => Integer`: return count keys at most per iteration
2351
+ #
2352
+ # @return [String, Array<[String, Float]>] the next cursor and all found
2353
+ # members and scores
2354
+ def zscan(key, cursor, options={})
2355
+ _scan(:zscan, cursor, [key], options) do |reply|
2356
+ [reply[0], _floatify_pairs.call(reply[1])]
2357
+ end
2358
+ end
2359
+
2360
+ # Scan a sorted set
2361
+ #
2362
+ # @example Retrieve all of the members/scores in a sorted set
2363
+ # redis.zscan_each("zset").to_a
2364
+ # # => [["key70", "70"], ["key80", "80"]]
2365
+ #
2366
+ # @param [Hash] options
2367
+ # - `:match => String`: only return keys matching the pattern
2368
+ # - `:count => Integer`: return count keys at most per iteration
2369
+ #
2370
+ # @return [Enumerator] an enumerator for all found scores and members
2371
+ def zscan_each(key, options={}, &block)
2372
+ return to_enum(:zscan_each, key, options) unless block_given?
2373
+ cursor = 0
2374
+ loop do
2375
+ cursor, values = zscan(key, cursor, options)
2376
+ values.each(&block)
2377
+ break if cursor == "0"
2378
+ end
2379
+ end
2380
+
2381
+ # Scan a set
2382
+ #
2383
+ # @example Retrieve the first batch of keys in a set
2384
+ # redis.sscan("set", 0)
2385
+ #
2386
+ # @param [String, Integer] cursor: the cursor of the iteration
2387
+ # @param [Hash] options
2388
+ # - `:match => String`: only return keys matching the pattern
2389
+ # - `:count => Integer`: return count keys at most per iteration
2390
+ #
2391
+ # @return [String, Array<String>] the next cursor and all found members
2392
+ def sscan(key, cursor, options={})
2393
+ _scan(:sscan, cursor, [key], options)
2394
+ end
2395
+
2396
+ # Scan a set
2397
+ #
2398
+ # @example Retrieve all of the keys in a set
2399
+ # redis.sscan("set").to_a
2400
+ # # => ["key1", "key2", "key3"]
2401
+ #
2402
+ # @param [Hash] options
2403
+ # - `:match => String`: only return keys matching the pattern
2404
+ # - `:count => Integer`: return count keys at most per iteration
2405
+ #
2406
+ # @return [Enumerator] an enumerator for all keys in the set
2407
+ def sscan_each(key, options={}, &block)
2408
+ return to_enum(:sscan_each, key, options) unless block_given?
2409
+ cursor = 0
2410
+ loop do
2411
+ cursor, keys = sscan(key, cursor, options)
2412
+ keys.each(&block)
2413
+ break if cursor == "0"
2283
2414
  end
2284
2415
  end
2285
2416
 
2417
+ def id
2418
+ @original_client.id
2419
+ end
2420
+
2421
+ def inspect
2422
+ "#<Redis client v#{Redis::VERSION} for #{id}>"
2423
+ end
2424
+
2286
2425
  def method_missing(command, *args)
2287
2426
  synchronize do |client|
2288
2427
  client.call([command] + args)
@@ -2320,12 +2459,30 @@ private
2320
2459
  }
2321
2460
  end
2322
2461
 
2323
- def _floatify(str)
2324
- if (inf = str.match(/^(-)?inf/i))
2325
- (inf[1] ? -1.0 : 1.0) / 0.0
2326
- else
2327
- Float str
2328
- end
2462
+ def _floatify
2463
+ lambda { |str|
2464
+ return unless str
2465
+
2466
+ if (inf = str.match(/^(-)?inf/i))
2467
+ (inf[1] ? -1.0 : 1.0) / 0.0
2468
+ else
2469
+ Float(str)
2470
+ end
2471
+ }
2472
+ end
2473
+
2474
+ def _floatify_pairs
2475
+ lambda { |array|
2476
+ return unless array
2477
+
2478
+ array.each_slice(2).map do |member, score|
2479
+ [member, _floatify.call(score)]
2480
+ end
2481
+ }
2482
+ end
2483
+
2484
+ def _pairify(array)
2485
+ array.each_slice(2).to_a
2329
2486
  end
2330
2487
 
2331
2488
  def _subscription(method, channels, block)