redis 4.5.0 → 4.7.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.
@@ -0,0 +1,411 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Keys
6
+ # Scan the keyspace
7
+ #
8
+ # @example Retrieve the first batch of keys
9
+ # redis.scan(0)
10
+ # # => ["4", ["key:21", "key:47", "key:42"]]
11
+ # @example Retrieve a batch of keys matching a pattern
12
+ # redis.scan(4, :match => "key:1?")
13
+ # # => ["92", ["key:13", "key:18"]]
14
+ # @example Retrieve a batch of keys of a certain type
15
+ # redis.scan(92, :type => "zset")
16
+ # # => ["173", ["sortedset:14", "sortedset:78"]]
17
+ #
18
+ # @param [String, Integer] cursor the cursor of the iteration
19
+ # @param [Hash] options
20
+ # - `:match => String`: only return keys matching the pattern
21
+ # - `:count => Integer`: return count keys at most per iteration
22
+ # - `:type => String`: return keys only of the given type
23
+ #
24
+ # @return [String, Array<String>] the next cursor and all found keys
25
+ def scan(cursor, **options)
26
+ _scan(:scan, cursor, [], **options)
27
+ end
28
+
29
+ # Scan the keyspace
30
+ #
31
+ # @example Retrieve all of the keys (with possible duplicates)
32
+ # redis.scan_each.to_a
33
+ # # => ["key:21", "key:47", "key:42"]
34
+ # @example Execute block for each key matching a pattern
35
+ # redis.scan_each(:match => "key:1?") {|key| puts key}
36
+ # # => key:13
37
+ # # => key:18
38
+ # @example Execute block for each key of a type
39
+ # redis.scan_each(:type => "hash") {|key| puts redis.type(key)}
40
+ # # => "hash"
41
+ # # => "hash"
42
+ #
43
+ # @param [Hash] options
44
+ # - `:match => String`: only return keys matching the pattern
45
+ # - `:count => Integer`: return count keys at most per iteration
46
+ # - `:type => String`: return keys only of the given type
47
+ #
48
+ # @return [Enumerator] an enumerator for all found keys
49
+ def scan_each(**options, &block)
50
+ return to_enum(:scan_each, **options) unless block_given?
51
+
52
+ cursor = 0
53
+ loop do
54
+ cursor, keys = scan(cursor, **options)
55
+ keys.each(&block)
56
+ break if cursor == "0"
57
+ end
58
+ end
59
+
60
+ # Remove the expiration from a key.
61
+ #
62
+ # @param [String] key
63
+ # @return [Boolean] whether the timeout was removed or not
64
+ def persist(key)
65
+ send_command([:persist, key], &Boolify)
66
+ end
67
+
68
+ # Set a key's time to live in seconds.
69
+ #
70
+ # @param [String] key
71
+ # @param [Integer] seconds time to live
72
+ # @return [Boolean] whether the timeout was set or not
73
+ def expire(key, seconds)
74
+ send_command([:expire, key, seconds], &Boolify)
75
+ end
76
+
77
+ # Set the expiration for a key as a UNIX timestamp.
78
+ #
79
+ # @param [String] key
80
+ # @param [Integer] unix_time expiry time specified as a UNIX timestamp
81
+ # @return [Boolean] whether the timeout was set or not
82
+ def expireat(key, unix_time)
83
+ send_command([:expireat, key, unix_time], &Boolify)
84
+ end
85
+
86
+ # Get the time to live (in seconds) for a key.
87
+ #
88
+ # @param [String] key
89
+ # @return [Integer] remaining time to live in seconds.
90
+ #
91
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
92
+ # the key exist but has no associated expire.
93
+ #
94
+ # Starting with Redis 2.8 the return value in case of error changed:
95
+ #
96
+ # - The command returns -2 if the key does not exist.
97
+ # - The command returns -1 if the key exists but has no associated expire.
98
+ def ttl(key)
99
+ send_command([:ttl, key])
100
+ end
101
+
102
+ # Set a key's time to live in milliseconds.
103
+ #
104
+ # @param [String] key
105
+ # @param [Integer] milliseconds time to live
106
+ # @return [Boolean] whether the timeout was set or not
107
+ def pexpire(key, milliseconds)
108
+ send_command([:pexpire, key, milliseconds], &Boolify)
109
+ end
110
+
111
+ # Set the expiration for a key as number of milliseconds from UNIX Epoch.
112
+ #
113
+ # @param [String] key
114
+ # @param [Integer] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch.
115
+ # @return [Boolean] whether the timeout was set or not
116
+ def pexpireat(key, ms_unix_time)
117
+ send_command([:pexpireat, key, ms_unix_time], &Boolify)
118
+ end
119
+
120
+ # Get the time to live (in milliseconds) for a key.
121
+ #
122
+ # @param [String] key
123
+ # @return [Integer] remaining time to live in milliseconds
124
+ # In Redis 2.6 or older the command returns -1 if the key does not exist or if
125
+ # the key exist but has no associated expire.
126
+ #
127
+ # Starting with Redis 2.8 the return value in case of error changed:
128
+ #
129
+ # - The command returns -2 if the key does not exist.
130
+ # - The command returns -1 if the key exists but has no associated expire.
131
+ def pttl(key)
132
+ send_command([:pttl, key])
133
+ end
134
+
135
+ # Return a serialized version of the value stored at a key.
136
+ #
137
+ # @param [String] key
138
+ # @return [String] serialized_value
139
+ def dump(key)
140
+ send_command([:dump, key])
141
+ end
142
+
143
+ # Create a key using the serialized value, previously obtained using DUMP.
144
+ #
145
+ # @param [String] key
146
+ # @param [String] ttl
147
+ # @param [String] serialized_value
148
+ # @param [Hash] options
149
+ # - `:replace => Boolean`: if false, raises an error if key already exists
150
+ # @raise [Redis::CommandError]
151
+ # @return [String] `"OK"`
152
+ def restore(key, ttl, serialized_value, replace: nil)
153
+ args = [:restore, key, ttl, serialized_value]
154
+ args << 'REPLACE' if replace
155
+
156
+ send_command(args)
157
+ end
158
+
159
+ # Transfer a key from the connected instance to another instance.
160
+ #
161
+ # @param [String, Array<String>] key
162
+ # @param [Hash] options
163
+ # - `:host => String`: host of instance to migrate to
164
+ # - `:port => Integer`: port of instance to migrate to
165
+ # - `:db => Integer`: database to migrate to (default: same as source)
166
+ # - `:timeout => Integer`: timeout (default: same as connection timeout)
167
+ # - `:copy => Boolean`: Do not remove the key from the local instance.
168
+ # - `:replace => Boolean`: Replace existing key on the remote instance.
169
+ # @return [String] `"OK"`
170
+ def migrate(key, options)
171
+ args = [:migrate]
172
+ args << (options[:host] || raise(':host not specified'))
173
+ args << (options[:port] || raise(':port not specified'))
174
+ args << (key.is_a?(String) ? key : '')
175
+ args << (options[:db] || @client.db).to_i
176
+ args << (options[:timeout] || @client.timeout).to_i
177
+ args << 'COPY' if options[:copy]
178
+ args << 'REPLACE' if options[:replace]
179
+ args += ['KEYS', *key] if key.is_a?(Array)
180
+
181
+ send_command(args)
182
+ end
183
+
184
+ # Delete one or more keys.
185
+ #
186
+ # @param [String, Array<String>] keys
187
+ # @return [Integer] number of keys that were deleted
188
+ def del(*keys)
189
+ keys.flatten!(1)
190
+ return 0 if keys.empty?
191
+
192
+ send_command([:del] + keys)
193
+ end
194
+
195
+ # Unlink one or more keys.
196
+ #
197
+ # @param [String, Array<String>] keys
198
+ # @return [Integer] number of keys that were unlinked
199
+ def unlink(*keys)
200
+ send_command([:unlink] + keys)
201
+ end
202
+
203
+ # Determine how many of the keys exists.
204
+ #
205
+ # @param [String, Array<String>] keys
206
+ # @return [Integer]
207
+ def exists(*keys)
208
+ if !Redis.exists_returns_integer && keys.size == 1
209
+ if Redis.exists_returns_integer.nil?
210
+ message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3. `exists?` returns a boolean, you " \
211
+ "should use it instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = " \
212
+ "true. To disable this message and keep the current (boolean) behaviour of 'exists' you can set " \
213
+ "`Redis.exists_returns_integer = false`, but this option will be removed in 5.0.0. " \
214
+ "(#{::Kernel.caller(1, 1).first})\n"
215
+
216
+ ::Redis.deprecate!(message)
217
+ end
218
+
219
+ exists?(*keys)
220
+ else
221
+ _exists(*keys)
222
+ end
223
+ end
224
+
225
+ def _exists(*keys)
226
+ send_command([:exists, *keys])
227
+ end
228
+
229
+ # Determine if any of the keys exists.
230
+ #
231
+ # @param [String, Array<String>] keys
232
+ # @return [Boolean]
233
+ def exists?(*keys)
234
+ send_command([:exists, *keys]) do |value|
235
+ value > 0
236
+ end
237
+ end
238
+
239
+ # Find all keys matching the given pattern.
240
+ #
241
+ # @param [String] pattern
242
+ # @return [Array<String>]
243
+ def keys(pattern = "*")
244
+ send_command([:keys, pattern]) do |reply|
245
+ if reply.is_a?(String)
246
+ reply.split(" ")
247
+ else
248
+ reply
249
+ end
250
+ end
251
+ end
252
+
253
+ # Move a key to another database.
254
+ #
255
+ # @example Move a key to another database
256
+ # redis.set "foo", "bar"
257
+ # # => "OK"
258
+ # redis.move "foo", 2
259
+ # # => true
260
+ # redis.exists "foo"
261
+ # # => false
262
+ # redis.select 2
263
+ # # => "OK"
264
+ # redis.exists "foo"
265
+ # # => true
266
+ # redis.get "foo"
267
+ # # => "bar"
268
+ #
269
+ # @param [String] key
270
+ # @param [Integer] db
271
+ # @return [Boolean] whether the key was moved or not
272
+ def move(key, db)
273
+ send_command([:move, key, db], &Boolify)
274
+ end
275
+
276
+ # Copy a value from one key to another.
277
+ #
278
+ # @example Copy a value to another key
279
+ # redis.set "foo", "value"
280
+ # # => "OK"
281
+ # redis.copy "foo", "bar"
282
+ # # => true
283
+ # redis.get "bar"
284
+ # # => "value"
285
+ #
286
+ # @example Copy a value to a key in another database
287
+ # redis.set "foo", "value"
288
+ # # => "OK"
289
+ # redis.copy "foo", "bar", db: 2
290
+ # # => true
291
+ # redis.select 2
292
+ # # => "OK"
293
+ # redis.get "bar"
294
+ # # => "value"
295
+ #
296
+ # @param [String] source
297
+ # @param [String] destination
298
+ # @param [Integer] db
299
+ # @param [Boolean] replace removes the `destination` key before copying value to it
300
+ # @return [Boolean] whether the key was copied or not
301
+ def copy(source, destination, db: nil, replace: false)
302
+ command = [:copy, source, destination]
303
+ command << "DB" << db if db
304
+ command << "REPLACE" if replace
305
+
306
+ send_command(command, &Boolify)
307
+ end
308
+
309
+ def object(*args)
310
+ send_command([:object] + args)
311
+ end
312
+
313
+ # Return a random key from the keyspace.
314
+ #
315
+ # @return [String]
316
+ def randomkey
317
+ send_command([:randomkey])
318
+ end
319
+
320
+ # Rename a key. If the new key already exists it is overwritten.
321
+ #
322
+ # @param [String] old_name
323
+ # @param [String] new_name
324
+ # @return [String] `OK`
325
+ def rename(old_name, new_name)
326
+ send_command([:rename, old_name, new_name])
327
+ end
328
+
329
+ # Rename a key, only if the new key does not exist.
330
+ #
331
+ # @param [String] old_name
332
+ # @param [String] new_name
333
+ # @return [Boolean] whether the key was renamed or not
334
+ def renamenx(old_name, new_name)
335
+ send_command([:renamenx, old_name, new_name], &Boolify)
336
+ end
337
+
338
+ # Sort the elements in a list, set or sorted set.
339
+ #
340
+ # @example Retrieve the first 2 elements from an alphabetically sorted "list"
341
+ # redis.sort("list", :order => "alpha", :limit => [0, 2])
342
+ # # => ["a", "b"]
343
+ # @example Store an alphabetically descending list in "target"
344
+ # redis.sort("list", :order => "desc alpha", :store => "target")
345
+ # # => 26
346
+ #
347
+ # @param [String] key
348
+ # @param [Hash] options
349
+ # - `:by => String`: use external key to sort elements by
350
+ # - `:limit => [offset, count]`: skip `offset` elements, return a maximum
351
+ # of `count` elements
352
+ # - `:get => [String, Array<String>]`: single key or array of keys to
353
+ # retrieve per element in the result
354
+ # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA`
355
+ # - `:store => String`: key to store the result at
356
+ #
357
+ # @return [Array<String>, Array<Array<String>>, Integer]
358
+ # - when `:get` is not specified, or holds a single element, an array of elements
359
+ # - when `:get` is specified, and holds more than one element, an array of
360
+ # elements where every element is an array with the result for every
361
+ # element specified in `:get`
362
+ # - when `:store` is specified, the number of elements in the stored result
363
+ def sort(key, by: nil, limit: nil, get: nil, order: nil, store: nil)
364
+ args = [:sort, key]
365
+ args << "BY" << by if by
366
+
367
+ if limit
368
+ args << "LIMIT"
369
+ args.concat(limit)
370
+ end
371
+
372
+ get = Array(get)
373
+ get.each do |item|
374
+ args << "GET" << item
375
+ end
376
+
377
+ args.concat(order.split(" ")) if order
378
+ args << "STORE" << store if store
379
+
380
+ send_command(args) do |reply|
381
+ if get.size > 1 && !store
382
+ reply.each_slice(get.size).to_a if reply
383
+ else
384
+ reply
385
+ end
386
+ end
387
+ end
388
+
389
+ # Determine the type stored at key.
390
+ #
391
+ # @param [String] key
392
+ # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none`
393
+ def type(key)
394
+ send_command([:type, key])
395
+ end
396
+
397
+ private
398
+
399
+ def _scan(command, cursor, args, match: nil, count: nil, type: nil, &block)
400
+ # SSCAN/ZSCAN/HSCAN already prepend the key to +args+.
401
+
402
+ args << cursor
403
+ args << "MATCH" << match if match
404
+ args << "COUNT" << count if count
405
+ args << "TYPE" << type if type
406
+
407
+ send_command([command] + args, &block)
408
+ end
409
+ end
410
+ end
411
+ end
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Redis
4
+ module Commands
5
+ module Lists
6
+ # Get the length of a list.
7
+ #
8
+ # @param [String] key
9
+ # @return [Integer]
10
+ def llen(key)
11
+ send_command([:llen, key])
12
+ end
13
+
14
+ # Remove the first/last element in a list, append/prepend it to another list and return it.
15
+ #
16
+ # @param [String] source source key
17
+ # @param [String] destination destination key
18
+ # @param [String, Symbol] where_source from where to remove the element from the source list
19
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
20
+ # @param [String, Symbol] where_destination where to push the element to the source list
21
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
22
+ #
23
+ # @return [nil, String] the element, or nil when the source key does not exist
24
+ #
25
+ # @note This command comes in place of the now deprecated RPOPLPUSH.
26
+ # Doing LMOVE RIGHT LEFT is equivalent.
27
+ def lmove(source, destination, where_source, where_destination)
28
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
29
+
30
+ send_command([:lmove, source, destination, where_source, where_destination])
31
+ end
32
+
33
+ # Remove the first/last element in a list and append/prepend it
34
+ # to another list and return it, or block until one is available.
35
+ #
36
+ # @example With timeout
37
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT", timeout: 5)
38
+ # # => nil on timeout
39
+ # # => "element" on success
40
+ # @example Without timeout
41
+ # element = redis.blmove("foo", "bar", "LEFT", "RIGHT")
42
+ # # => "element"
43
+ #
44
+ # @param [String] source source key
45
+ # @param [String] destination destination key
46
+ # @param [String, Symbol] where_source from where to remove the element from the source list
47
+ # e.g. 'LEFT' - from head, 'RIGHT' - from tail
48
+ # @param [String, Symbol] where_destination where to push the element to the source list
49
+ # e.g. 'LEFT' - to head, 'RIGHT' - to tail
50
+ # @param [Hash] options
51
+ # - `:timeout => Numeric`: timeout in seconds, defaults to no timeout
52
+ #
53
+ # @return [nil, String] the element, or nil when the source key does not exist or the timeout expired
54
+ #
55
+ def blmove(source, destination, where_source, where_destination, timeout: 0)
56
+ where_source, where_destination = _normalize_move_wheres(where_source, where_destination)
57
+
58
+ command = [:blmove, source, destination, where_source, where_destination, timeout]
59
+ send_blocking_command(command, timeout)
60
+ end
61
+
62
+ # Prepend one or more values to a list, creating the list if it doesn't exist
63
+ #
64
+ # @param [String] key
65
+ # @param [String, Array<String>] value string value, or array of string values to push
66
+ # @return [Integer] the length of the list after the push operation
67
+ def lpush(key, value)
68
+ send_command([:lpush, key, value])
69
+ end
70
+
71
+ # Prepend a value to a list, only if the list exists.
72
+ #
73
+ # @param [String] key
74
+ # @param [String] value
75
+ # @return [Integer] the length of the list after the push operation
76
+ def lpushx(key, value)
77
+ send_command([:lpushx, key, value])
78
+ end
79
+
80
+ # Append one or more values to a list, creating the list if it doesn't exist
81
+ #
82
+ # @param [String] key
83
+ # @param [String, Array<String>] value string value, or array of string values to push
84
+ # @return [Integer] the length of the list after the push operation
85
+ def rpush(key, value)
86
+ send_command([:rpush, key, value])
87
+ end
88
+
89
+ # Append a value to a list, only if the list exists.
90
+ #
91
+ # @param [String] key
92
+ # @param [String] value
93
+ # @return [Integer] the length of the list after the push operation
94
+ def rpushx(key, value)
95
+ send_command([:rpushx, key, value])
96
+ end
97
+
98
+ # Remove and get the first elements in a list.
99
+ #
100
+ # @param [String] key
101
+ # @param [Integer] count number of elements to remove
102
+ # @return [String, Array<String>] the values of the first elements
103
+ def lpop(key, count = nil)
104
+ command = [:lpop, key]
105
+ command << count if count
106
+ send_command(command)
107
+ end
108
+
109
+ # Remove and get the last elements in a list.
110
+ #
111
+ # @param [String] key
112
+ # @param [Integer] count number of elements to remove
113
+ # @return [String, Array<String>] the values of the last elements
114
+ def rpop(key, count = nil)
115
+ command = [:rpop, key]
116
+ command << count if count
117
+ send_command(command)
118
+ end
119
+
120
+ # Remove the last element in a list, append it to another list and return it.
121
+ #
122
+ # @param [String] source source key
123
+ # @param [String] destination destination key
124
+ # @return [nil, String] the element, or nil when the source key does not exist
125
+ def rpoplpush(source, destination)
126
+ send_command([:rpoplpush, source, destination])
127
+ end
128
+
129
+ # Remove and get the first element in a list, or block until one is available.
130
+ #
131
+ # @example With timeout
132
+ # list, element = redis.blpop("list", :timeout => 5)
133
+ # # => nil on timeout
134
+ # # => ["list", "element"] on success
135
+ # @example Without timeout
136
+ # list, element = redis.blpop("list")
137
+ # # => ["list", "element"]
138
+ # @example Blocking pop on multiple lists
139
+ # list, element = redis.blpop(["list", "another_list"])
140
+ # # => ["list", "element"]
141
+ #
142
+ # @param [String, Array<String>] keys one or more keys to perform the
143
+ # blocking pop on
144
+ # @param [Hash] options
145
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
146
+ #
147
+ # @return [nil, [String, String]]
148
+ # - `nil` when the operation timed out
149
+ # - tuple of the list that was popped from and element was popped otherwise
150
+ def blpop(*args)
151
+ _bpop(:blpop, args)
152
+ end
153
+
154
+ # Remove and get the last element in a list, or block until one is available.
155
+ #
156
+ # @param [String, Array<String>] keys one or more keys to perform the
157
+ # blocking pop on
158
+ # @param [Hash] options
159
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
160
+ #
161
+ # @return [nil, [String, String]]
162
+ # - `nil` when the operation timed out
163
+ # - tuple of the list that was popped from and element was popped otherwise
164
+ #
165
+ # @see #blpop
166
+ def brpop(*args)
167
+ _bpop(:brpop, args)
168
+ end
169
+
170
+ # Pop a value from a list, push it to another list and return it; or block
171
+ # until one is available.
172
+ #
173
+ # @param [String] source source key
174
+ # @param [String] destination destination key
175
+ # @param [Hash] options
176
+ # - `:timeout => Integer`: timeout in seconds, defaults to no timeout
177
+ #
178
+ # @return [nil, String]
179
+ # - `nil` when the operation timed out
180
+ # - the element was popped and pushed otherwise
181
+ def brpoplpush(source, destination, deprecated_timeout = 0, timeout: deprecated_timeout)
182
+ command = [:brpoplpush, source, destination, timeout]
183
+ send_blocking_command(command, timeout)
184
+ end
185
+
186
+ # Get an element from a list by its index.
187
+ #
188
+ # @param [String] key
189
+ # @param [Integer] index
190
+ # @return [String]
191
+ def lindex(key, index)
192
+ send_command([:lindex, key, index])
193
+ end
194
+
195
+ # Insert an element before or after another element in a list.
196
+ #
197
+ # @param [String] key
198
+ # @param [String, Symbol] where `BEFORE` or `AFTER`
199
+ # @param [String] pivot reference element
200
+ # @param [String] value
201
+ # @return [Integer] length of the list after the insert operation, or `-1`
202
+ # when the element `pivot` was not found
203
+ def linsert(key, where, pivot, value)
204
+ send_command([:linsert, key, where, pivot, value])
205
+ end
206
+
207
+ # Get a range of elements from a list.
208
+ #
209
+ # @param [String] key
210
+ # @param [Integer] start start index
211
+ # @param [Integer] stop stop index
212
+ # @return [Array<String>]
213
+ def lrange(key, start, stop)
214
+ send_command([:lrange, key, start, stop])
215
+ end
216
+
217
+ # Remove elements from a list.
218
+ #
219
+ # @param [String] key
220
+ # @param [Integer] count number of elements to remove. Use a positive
221
+ # value to remove the first `count` occurrences of `value`. A negative
222
+ # value to remove the last `count` occurrences of `value`. Or zero, to
223
+ # remove all occurrences of `value` from the list.
224
+ # @param [String] value
225
+ # @return [Integer] the number of removed elements
226
+ def lrem(key, count, value)
227
+ send_command([:lrem, key, count, value])
228
+ end
229
+
230
+ # Set the value of an element in a list by its index.
231
+ #
232
+ # @param [String] key
233
+ # @param [Integer] index
234
+ # @param [String] value
235
+ # @return [String] `OK`
236
+ def lset(key, index, value)
237
+ send_command([:lset, key, index, value])
238
+ end
239
+
240
+ # Trim a list to the specified range.
241
+ #
242
+ # @param [String] key
243
+ # @param [Integer] start start index
244
+ # @param [Integer] stop stop index
245
+ # @return [String] `OK`
246
+ def ltrim(key, start, stop)
247
+ send_command([:ltrim, key, start, stop])
248
+ end
249
+
250
+ private
251
+
252
+ def _bpop(cmd, args, &blk)
253
+ timeout = if args.last.is_a?(Hash)
254
+ options = args.pop
255
+ options[:timeout]
256
+ elsif args.last.respond_to?(:to_int)
257
+ # Issue deprecation notice in obnoxious mode...
258
+ args.pop.to_int
259
+ end
260
+
261
+ timeout ||= 0
262
+
263
+ if args.size > 1
264
+ # Issue deprecation notice in obnoxious mode...
265
+ end
266
+
267
+ keys = args.flatten
268
+
269
+ command = [cmd, keys, timeout]
270
+ send_blocking_command(command, timeout, &blk)
271
+ end
272
+
273
+ def _normalize_move_wheres(where_source, where_destination)
274
+ where_source = where_source.to_s.upcase
275
+ where_destination = where_destination.to_s.upcase
276
+
277
+ if where_source != "LEFT" && where_source != "RIGHT"
278
+ raise ArgumentError, "where_source must be 'LEFT' or 'RIGHT'"
279
+ end
280
+
281
+ if where_destination != "LEFT" && where_destination != "RIGHT"
282
+ raise ArgumentError, "where_destination must be 'LEFT' or 'RIGHT'"
283
+ end
284
+
285
+ [where_source, where_destination]
286
+ end
287
+ end
288
+ end
289
+ end