lib/redis.rb in redis-3.0.0.rc1 vs lib/redis.rb in redis-3.0.0.rc2

- old
+ new

@@ -7,94 +7,77 @@ $stderr.puts "\n#{message} (in #{trace})" end attr :client + # @deprecated The preferred way to create a new client object is using `#new`. + # This method does not actually establish a connection to Redis, + # in contrary to what you might expect. def self.connect(options = {}) - options = options.dup - - url = options.delete(:url) || ENV["REDIS_URL"] - if url - require "uri" - - uri = URI(url) - - # Require the URL to have at least a host - raise ArgumentError, "invalid url" unless uri.host - - options[:host] ||= uri.host - options[:port] ||= uri.port - options[:password] ||= uri.password - options[:db] ||= uri.path[1..-1].to_i - end - new(options) end def self.current - Thread.current[:redis] ||= Redis.connect + @current ||= Redis.new end def self.current=(redis) - Thread.current[:redis] = redis + @current = redis end include MonitorMixin def initialize(options = {}) @client = Client.new(options) - if options[:thread_safe] == false - @synchronizer = lambda { |&block| block.call } - else - @synchronizer = lambda { |&block| mon_synchronize { block.call } } - super() # Monitor#initialize - end + super() # Monitor#initialize end def synchronize - @synchronizer.call { yield } + mon_synchronize { yield(@client) } end # Run code without the client reconnecting def without_reconnect(&block) - synchronize do - @client.without_reconnect(&block) + synchronize do |client| + client.without_reconnect(&block) end end # Authenticate to the server. # # @param [String] password must match the password specified in the # `requirepass` directive in the configuration file # @return [String] `OK` def auth(password) - synchronize do - @client.call [:auth, password] + synchronize do |client| + client.call [:auth, password] end end # Change the selected database for the current connection. # # @param [Fixnum] db zero-based index of the DB to use (0 to 15) # @return [String] `OK` def select(db) - synchronize do - @client.db = db - @client.call [:select, db] + synchronize do |client| + client.db = db + client.call [:select, db] end end # Get information and statistics about the server. # # @param [String, Symbol] cmd e.g. "commandstats" # @return [Hash<String, String>] def info(cmd = nil) - synchronize do - @client.call [:info, cmd].compact do |reply| + synchronize do |client| + client.call [:info, cmd].compact do |reply| if reply.kind_of?(String) - reply = Hash[*reply.split(/:|\r\n/).grep(/^[^#]/)] + reply = Hash[reply.split("\r\n").map do |line| + line.split(":", 2) unless line =~ /^(#|$)/ + end] if cmd && cmd.to_s == "commandstats" # Extract nested hashes for INFO COMMANDSTATS reply = Hash[reply.map do |k, v| [k[/^cmdstat_(.*)$/, 1], Hash[*v.split(/,|=/)]] @@ -111,12 +94,12 @@ # # @param [String] action e.g. `get`, `set`, `resetstat` # @return [String, Hash] string reply, or hash when retrieving more than one # property with `CONFIG GET` def config(action, *args) - synchronize do - @client.call [:config, action, *args] do |reply| + synchronize do |client| + client.call [:config, action, *args] do |reply| if reply.kind_of?(Array) && action == :get Hash[*reply] else reply end @@ -126,58 +109,58 @@ # Remove all keys from the current database. # # @return [String] `OK` def flushdb - synchronize do - @client.call [:flushdb] + synchronize do |client| + client.call [:flushdb] end end # Remove all keys from all databases. # # @return [String] `OK` def flushall - synchronize do - @client.call [:flushall] + synchronize do |client| + client.call [:flushall] end end # Synchronously save the dataset to disk. # # @return [String] def save - synchronize do - @client.call [:save] + synchronize do |client| + client.call [:save] end end # Asynchronously save the dataset to disk. # # @return [String] `OK` def bgsave - synchronize do - @client.call [:bgsave] + synchronize do |client| + client.call [:bgsave] end end # Asynchronously rewrite the append-only file. # # @return [String] `OK` def bgrewriteaof - synchronize do - @client.call [:bgrewriteaof] + synchronize do |client| + client.call [:bgrewriteaof] end end # Get the value of a key. # # @param [String] key # @return [String] def get(key) - synchronize do - @client.call [:get, key] + synchronize do |client| + client.call [:get, key] end end alias :[] :get @@ -185,12 +168,12 @@ # # @param [String] key # @param [Fixnum] offset bit offset # @return [Fixnum] `0` or `1` def getbit(key, offset) - synchronize do - @client.call [:getbit, key, offset] + synchronize do |client| + client.call [:getbit, key, offset] end end # Get a substring of the string stored at a key. # @@ -198,118 +181,114 @@ # @param [Fixnum] start zero-based start offset # @param [Fixnum] stop zero-based end offset. Use -1 for representing # the end of the string # @return [Fixnum] `0` or `1` def getrange(key, start, stop) - synchronize do - @client.call [:getrange, key, start, stop] + synchronize do |client| + client.call [:getrange, key, start, stop] end end # Set the string value of a key and return its old value. # # @param [String] key # @param [String] value value to replace the current value with # @return [String] the old value stored in the key, or `nil` if the key # did not exist def getset(key, value) - synchronize do - @client.call [:getset, key, value] + synchronize do |client| + client.call [:getset, key, value] end end # Get the values of all the given keys. # + # @example + # redis.mget("key1", "key1") + # # => ["v1", "v2"] + # # @param [Array<String>] keys - # @return [Array<String>] + # @return [Array<String>] an array of values for the specified keys + # + # @see #mapped_mget def mget(*keys, &blk) - synchronize do - @client.call [:mget, *keys], &blk + synchronize do |client| + client.call [:mget, *keys], &blk end end # Append a value to a key. # # @param [String] key # @param [String] value value to append # @return [Fixnum] length of the string after appending def append(key, value) - synchronize do - @client.call [:append, key, value] + synchronize do |client| + client.call [:append, key, value] end end # Get the length of the value stored in a key. # # @param [String] key # @return [Fixnum] the length of the value stored in the key, or 0 # if the key does not exist def strlen(key) - synchronize do - @client.call [:strlen, key] + synchronize do |client| + client.call [:strlen, key] end end # Get all the fields and values in a hash. # # @param [String] key # @return [Hash<String, String>] def hgetall(key) - synchronize do - @client.call [:hgetall, key] do |reply| - if reply.kind_of?(Array) - hash = Hash.new - reply.each_slice(2) do |field, value| - hash[field] = value - end - hash - else - reply - end - end + synchronize do |client| + client.call [:hgetall, key], &_hashify end end # Get the value of a hash field. # # @param [String] key # @param [String] field # @return [String] def hget(key, field) - synchronize do - @client.call [:hget, key, field] + synchronize do |client| + client.call [:hget, key, field] end end # Delete one or more hash fields. # # @param [String] key # @param [String, Array<String>] field # @return [Fixnum] the number of fields that were removed from the hash def hdel(key, field) - synchronize do - @client.call [:hdel, key, field] + synchronize do |client| + client.call [:hdel, key, field] end end # Get all the fields in a hash. # # @param [String] key # @return [Array<String>] def hkeys(key) - synchronize do - @client.call [:hkeys, key] + synchronize do |client| + client.call [:hkeys, key] end end # Find all keys matching the given pattern. # # @param [String] pattern # @return [Array<String>] def keys(pattern = "*") - synchronize do - @client.call [:keys, pattern] do |reply| + synchronize do |client| + client.call [:keys, pattern] do |reply| if reply.kind_of?(String) reply.split(" ") else reply end @@ -319,104 +298,119 @@ # Return a random key from the keyspace. # # @return [String] def randomkey - synchronize do - @client.call [:randomkey] + synchronize do |client| + client.call [:randomkey] end end # Echo the given string. # # @param [String] value # @return [String] def echo(value) - synchronize do - @client.call [:echo, value] + synchronize do |client| + client.call [:echo, value] end end + # Return the server time. + # + # @example + # r.time # => [ 1333093196, 606806 ] + # + # @return [Array<Fixnum>] tuple of seconds since UNIX epoch and + # microseconds in the current second + def time + synchronize do |client| + client.call [:time] do |reply| + reply.map(&:to_i) if reply + end + end + end + # Ping the server. # # @return [String] `PONG` def ping - synchronize do - @client.call [:ping] + synchronize do |client| + client.call [:ping] end end # Get the UNIX time stamp of the last successful save to disk. # # @return [Fixnum] def lastsave - synchronize do - @client.call [:lastsave] + synchronize do |client| + client.call [:lastsave] end end # Return the number of keys in the selected database. # # @return [Fixnum] def dbsize - synchronize do - @client.call [:dbsize] + synchronize do |client| + client.call [:dbsize] end end # Determine if a key exists. # # @param [String] key # @return [Boolean] def exists(key) - synchronize do - @client.call [:exists, key], &_boolify + synchronize do |client| + client.call [:exists, key], &_boolify end end # Get the length of a list. # # @param [String] key # @return [Fixnum] def llen(key) - synchronize do - @client.call [:llen, key] + synchronize do |client| + client.call [:llen, key] end end # Get a range of elements from a list. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Array<String>] def lrange(key, start, stop) - synchronize do - @client.call [:lrange, key, start, stop] + synchronize do |client| + client.call [:lrange, key, start, stop] end end # Trim a list to the specified range. # # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [String] `OK` def ltrim(key, start, stop) - synchronize do - @client.call [:ltrim, key, start, stop] + synchronize do |client| + client.call [:ltrim, key, start, stop] end end # Get an element from a list by its index. # # @param [String] key # @param [Fixnum] index # @return [String] def lindex(key, index) - synchronize do - @client.call [:lindex, key, index] + synchronize do |client| + client.call [:lindex, key, index] end end # Insert an element before or after another element in a list. # @@ -425,24 +419,24 @@ # @param [String] pivot reference element # @param [String] value # @return [Fixnum] length of the list after the insert operation, or `-1` # when the element `pivot` was not found def linsert(key, where, pivot, value) - synchronize do - @client.call [:linsert, key, where, pivot, value] + synchronize do |client| + client.call [:linsert, key, where, pivot, value] end end # Set the value of an element in a list by its index. # # @param [String] key # @param [Fixnum] index # @param [String] value # @return [String] `OK` def lset(key, index, value) - synchronize do - @client.call [:lset, key, index, value] + synchronize do |client| + client.call [:lset, key, index, value] end end # Remove elements from a list. # @@ -452,158 +446,210 @@ # value to remove the last `count` occurrences of `value`. Or zero, to # remove all occurrences of `value` from the list. # @param [String] value # @return [Fixnum] the number of removed elements def lrem(key, count, value) - synchronize do - @client.call [:lrem, key, count, value] + synchronize do |client| + client.call [:lrem, key, count, value] end end # Append one or more values to a list, creating the list if it doesn't exist # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def rpush(key, value) - synchronize do - @client.call [:rpush, key, value] + synchronize do |client| + client.call [:rpush, key, value] end end # Append a value to a list, only if the list exists. # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def rpushx(key, value) - synchronize do - @client.call [:rpushx, key, value] + synchronize do |client| + client.call [:rpushx, key, value] end end # Prepend one or more values to a list, creating the list if it doesn't exist # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def lpush(key, value) - synchronize do - @client.call [:lpush, key, value] + synchronize do |client| + client.call [:lpush, key, value] end end # Prepend a value to a list, only if the list exists. # # @param [String] key # @param [String] value # @return [Fixnum] the length of the list after the push operation def lpushx(key, value) - synchronize do - @client.call [:lpushx, key, value] + synchronize do |client| + client.call [:lpushx, key, value] end end # Remove and get the last element in a list. # # @param [String] key # @return [String] def rpop(key) - synchronize do - @client.call [:rpop, key] + synchronize do |client| + client.call [:rpop, key] end end + def _bpop(cmd, args) + options = {} + + case args.last + when Hash + options = args.pop + when Integer + # Issue deprecation notice in obnoxious mode... + options[:timeout] = args.pop + end + + if args.size > 1 + # Issue deprecation notice in obnoxious mode... + end + + keys = args.flatten + timeout = options[:timeout] || 0 + + synchronize do |client| + client.call_without_timeout [cmd, keys, timeout] + end + end + # Remove and get the first element in a list, or block until one is available. # - # @param [Array<String>] args one or more keys to perform a blocking pop on, - # followed by a `Fixnum` timeout value - # @return [nil, Array<String>] tuple of list that was popped from and element - # that was popped, or nil when the blocking operation timed out + # @example With timeout + # list, element = redis.blpop("list", :timeout => 5) + # # => nil on timeout + # # => ["list", "element"] on success + # @example Without timeout + # list, element = redis.blpop("list") + # # => ["list", "element"] + # @example Blocking pop on multiple lists + # list, element = redis.blpop(["list", "another_list"]) + # # => ["list", "element"] + # + # @param [String, Array<String>] keys one or more keys to perform the + # blocking pop on + # @param [Hash] options + # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout + # + # @return [nil, [String, String]] + # - `nil` when the operation timed out + # - tuple of the list that was popped from and element was popped otherwise def blpop(*args) - synchronize do - @client.call_without_timeout [:blpop, *args] - end + _bpop(:blpop, args) end # Remove and get the last element in a list, or block until one is available. # - # @param [Array<String>] args one or more keys to perform a blocking pop on, - # followed by a `Fixnum` timeout value - # @return [nil, Array<String>] tuple of list that was popped from and element - # that was popped, or nil when the blocking operation timed out + # @param [String, Array<String>] keys one or more keys to perform the + # blocking pop on + # @param [Hash] options + # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout + # + # @return [nil, [String, String]] + # - `nil` when the operation timed out + # - tuple of the list that was popped from and element was popped otherwise + # + # @see #blpop def brpop(*args) - synchronize do - @client.call_without_timeout [:brpop, *args] - end + _bpop(:brpop, args) end # Pop a value from a list, push it to another list and return it; or block # until one is available. # # @param [String] source source key # @param [String] destination destination key - # @param [Fixnum] timeout - # @return [nil, String] the element, or nil when the blocking operation timed out - def brpoplpush(source, destination, timeout) - synchronize do - @client.call_without_timeout [:brpoplpush, source, destination, timeout] + # @param [Hash] options + # - `:timeout => Fixnum`: timeout in seconds, defaults to no timeout + # + # @return [nil, String] + # - `nil` when the operation timed out + # - the element was popped and pushed otherwise + def brpoplpush(source, destination, options = {}) + case options + when Integer + # Issue deprecation notice in obnoxious mode... + options = { :timeout => options } end + + timeout = options[:timeout] || 0 + + synchronize do |client| + client.call_without_timeout [:brpoplpush, source, destination, timeout] + end end # Remove the last element in a list, append it to another list and return it. # # @param [String] source source key # @param [String] destination destination key # @return [nil, String] the element, or nil when the source key does not exist def rpoplpush(source, destination) - synchronize do - @client.call [:rpoplpush, source, destination] + synchronize do |client| + client.call [:rpoplpush, source, destination] end end # Remove and get the first element in a list. # # @param [String] key # @return [String] def lpop(key) - synchronize do - @client.call [:lpop, key] + synchronize do |client| + client.call [:lpop, key] end end # Interact with the slowlog (get, len, reset) # # @param [String] subcommand e.g. `get`, `len`, `reset` # @param [Fixnum] length maximum number of entries to return # @return [Array<String>, Fixnum, String] depends on subcommand def slowlog(subcommand, length=nil) - synchronize do + synchronize do |client| args = [:slowlog, subcommand] args << length if length - @client.call args + client.call args end end # Get all the members in a set. # # @param [String] key # @return [Array<String>] def smembers(key) - synchronize do - @client.call [:smembers, key] + synchronize do |client| + client.call [:smembers, key] end end # Determine if a given value is a member of a set. # # @param [String] key # @param [String] member # @return [Boolean] def sismember(key, member) - synchronize do - @client.call [:sismember, key, member], &_boolify + synchronize do |client| + client.call [:sismember, key, member], &_boolify end end # Add one or more members to a set. # @@ -612,12 +658,12 @@ # @return [Boolean, Fixnum] `Boolean` when a single member is specified, # holding whether or not adding the member succeeded, or `Fixnum` when an # array of members is specified, holding the number of members that were # successfully added def sadd(key, member) - synchronize do - @client.call [:sadd, key, member] do |reply| + synchronize do |client| + client.call [:sadd, key, member] do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean @@ -634,12 +680,12 @@ # @return [Boolean, Fixnum] `Boolean` when a single member is specified, # holding whether or not removing the member succeeded, or `Fixnum` when an # array of members is specified, holding the number of members that were # successfully removed def srem(key, member) - synchronize do - @client.call [:srem, key, member] do |reply| + synchronize do |client| + client.call [:srem, key, member] do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean @@ -654,134 +700,134 @@ # @param [String] source source key # @param [String] destination destination key # @param [String] member member to move from `source` to `destination` # @return [Boolean] def smove(source, destination, member) - synchronize do - @client.call [:smove, source, destination, member], &_boolify + synchronize do |client| + client.call [:smove, source, destination, member], &_boolify end end # Remove and return a random member from a set. # # @param [String] key # @return [String] def spop(key) - synchronize do - @client.call [:spop, key] + synchronize do |client| + client.call [:spop, key] end end # Get the number of members in a set. # # @param [String] key # @return [Fixnum] def scard(key) - synchronize do - @client.call [:scard, key] + synchronize do |client| + client.call [:scard, key] end end # Intersect multiple sets. # # @param [String, Array<String>] keys keys pointing to sets to intersect # @return [Array<String>] members in the intersection def sinter(*keys) - synchronize do - @client.call [:sinter, *keys] + synchronize do |client| + client.call [:sinter, *keys] end end # Intersect multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array<String>] keys keys pointing to sets to intersect # @return [Fixnum] number of elements in the resulting set def sinterstore(destination, *keys) - synchronize do - @client.call [:sinterstore, destination, *keys] + synchronize do |client| + client.call [:sinterstore, destination, *keys] end end # Add multiple sets. # # @param [String, Array<String>] keys keys pointing to sets to unify # @return [Array<String>] members in the union def sunion(*keys) - synchronize do - @client.call [:sunion, *keys] + synchronize do |client| + client.call [:sunion, *keys] end end # Add multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array<String>] keys keys pointing to sets to unify # @return [Fixnum] number of elements in the resulting set def sunionstore(destination, *keys) - synchronize do - @client.call [:sunionstore, destination, *keys] + synchronize do |client| + client.call [:sunionstore, destination, *keys] end end # Subtract multiple sets. # # @param [String, Array<String>] keys keys pointing to sets to subtract # @return [Array<String>] members in the difference def sdiff(*keys) - synchronize do - @client.call [:sdiff, *keys] + synchronize do |client| + client.call [:sdiff, *keys] end end # Subtract multiple sets and store the resulting set in a key. # # @param [String] destination destination key # @param [String, Array<String>] keys keys pointing to sets to subtract # @return [Fixnum] number of elements in the resulting set def sdiffstore(destination, *keys) - synchronize do - @client.call [:sdiffstore, destination, *keys] + synchronize do |client| + client.call [:sdiffstore, destination, *keys] end end # Get a random member from a set. # # @param [String] key # @return [String] def srandmember(key) - synchronize do - @client.call [:srandmember, key] + synchronize do |client| + client.call [:srandmember, key] end end # Add one or more members to a sorted set, or update the score for members # that already exist. # - # @example Add a single `(score, member)` pair to a sorted set + # @example Add a single `[score, member]` pair to a sorted set # redis.zadd("zset", 32.0, "member") - # @example Add an array of `(score, member)` pairs to a sorted set + # @example Add an array of `[score, member]` pairs to a sorted set # redis.zadd("zset", [[32.0, "a"], [64.0, "b"]]) # # @param [String] key - # @param [(Float, String), Array<(Float,String)>] args - # - a single `(score, member)` pair - # - an array of `(score, member)` pairs + # @param [[Float, String], Array<[Float, String]>] args + # - a single `[score, member]` pair + # - an array of `[score, member]` pairs # # @return [Boolean, Fixnum] # - `Boolean` when a single pair is specified, holding whether or not it was # **added** to the sorted set # - `Fixnum` when an array of pairs is specified, holding the number of # pairs that were **added** to the sorted set def zadd(key, *args) - synchronize do + synchronize do |client| if args.size == 1 && args[0].is_a?(Array) # Variadic: return integer - @client.call [:zadd, key] + args[0] + client.call [:zadd, key] + args[0] elsif args.size == 2 # Single pair: return boolean - @client.call [:zadd, key, args[0], args[1]], &_boolify + client.call [:zadd, key, args[0], args[1]], &_boolify else raise ArgumentError, "wrong number of arguments" end end end @@ -802,12 +848,12 @@ # - `Boolean` when a single member is specified, holding whether or not it # was removed from the sorted set # - `Fixnum` when an array of pairs is specified, holding the number of # members that were removed to the sorted set def zrem(key, member) - synchronize do - @client.call [:zrem, key, member] do |reply| + synchronize do |client| + client.call [:zrem, key, member] do |reply| if member.is_a? Array # Variadic: return integer reply else # Single argument: return boolean @@ -821,24 +867,24 @@ # # @param [String] key # @param [String] member # @return [Fixnum] def zrank(key, member) - synchronize do - @client.call [:zrank, key, member] + synchronize do |client| + client.call [:zrank, key, member] end end # Determine the index of a member in a sorted set, with scores ordered from # high to low. # # @param [String] key # @param [String] member # @return [Fixnum] def zrevrank(key, member) - synchronize do - @client.call [:zrevrank, key, member] + synchronize do |client| + client.call [:zrevrank, key, member] end end # Increment the score of a member in a sorted set. # @@ -849,12 +895,12 @@ # @param [String] key # @param [Float] increment # @param [String] member # @return [Float] score of the member after incrementing it def zincrby(key, increment, member) - synchronize do - @client.call [:zincrby, key, increment, member] do |reply| + synchronize do |client| + client.call [:zincrby, key, increment, member] do |reply| Float(reply) if reply end end end @@ -865,12 +911,12 @@ # # => 4 # # @param [String] key # @return [Fixnum] def zcard(key) - synchronize do - @client.call [:zcard, key] + synchronize do |client| + client.call [:zcard, key] end end # Return a range of members in a sorted set, by index. # @@ -885,21 +931,21 @@ # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @param [Hash] options # - `:with_scores => true`: include scores in output # - # @return [Array<String>, Array<(String, Float)>] + # @return [Array<String>, Array<[String, Float]>] # - when `:with_scores` is not specified, an array of members - # - when `:with_scores` is specified, an array with `(member, score)` pairs + # - when `:with_scores` is specified, an array with `[member, score]` pairs def zrange(key, start, stop, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] args << "WITHSCORES" if with_scores - synchronize do - @client.call [:zrange, key, start, stop, *args] do |reply| + synchronize do |client| + client.call [:zrange, key, start, stop, *args] do |reply| if with_scores if reply reply.each_slice(2).map do |member, score| [member, Float(score)] end @@ -926,12 +972,12 @@ args = [] with_scores = options[:with_scores] || options[:withscores] args << "WITHSCORES" if with_scores - synchronize do - @client.call [:zrevrange, key, start, stop, *args] do |reply| + synchronize do |client| + client.call [:zrevrange, key, start, stop, *args] do |reply| if with_scores if reply reply.each_slice(2).map do |member, score| [member, Float(score)] end @@ -965,24 +1011,24 @@ # @param [Hash] options # - `:with_scores => true`: include scores in output # - `:limit => [offset, count]`: skip `offset` members, return a maximum of # `count` members # - # @return [Array<String>, Array<(String, Float)>] + # @return [Array<String>, Array<[String, Float]>] # - when `:with_scores` is not specified, an array of members - # - when `:with_scores` is specified, an array with `(member, score)` pairs + # - when `:with_scores` is specified, an array with `[member, score]` pairs def zrangebyscore(key, min, max, options = {}) args = [] with_scores = options[:with_scores] || options[:withscores] args.concat ["WITHSCORES"] if with_scores limit = options[:limit] args.concat ["LIMIT", *limit] if limit - synchronize do - @client.call [:zrangebyscore, key, min, max, *args] do |reply| + synchronize do |client| + client.call [:zrangebyscore, key, min, max, *args] do |reply| if with_scores if reply reply.each_slice(2).map do |member, score| [member, Float(score)] end @@ -1015,12 +1061,12 @@ args.concat ["WITHSCORES"] if with_scores limit = options[:limit] args.concat ["LIMIT", *limit] if limit - synchronize do - @client.call [:zrevrangebyscore, key, max, min, *args] do |reply| + synchronize do |client| + client.call [:zrevrangebyscore, key, max, min, *args] do |reply| if with_scores if reply reply.each_slice(2).map do |member, score| [member, Float(score)] end @@ -1047,13 +1093,13 @@ # - exclusive minimum score is specified by prefixing `(` # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @return [Fixnum] number of members in within the specified range - def zcount(key, start, stop) - synchronize do - @client.call [:zcount, key, start, stop] + def zcount(key, min, max) + synchronize do |client| + client.call [:zcount, key, min, max] end end # Remove all members in a sorted set within the given scores. # @@ -1071,12 +1117,12 @@ # @param [String] max # - inclusive maximum score is specified verbatim # - exclusive maximum score is specified by prefixing `(` # @return [Fixnum] number of members that were removed def zremrangebyscore(key, min, max) - synchronize do - @client.call [:zremrangebyscore, key, min, max] + synchronize do |client| + client.call [:zremrangebyscore, key, min, max] end end # Remove all members in a sorted set within the given indexes. # @@ -1090,12 +1136,12 @@ # @param [String] key # @param [Fixnum] start start index # @param [Fixnum] stop stop index # @return [Fixnum] number of members that were removed def zremrangebyrank(key, start, stop) - synchronize do - @client.call [:zremrangebyrank, key, start, stop] + synchronize do |client| + client.call [:zremrangebyrank, key, start, stop] end end # Get the score associated with the given member in a sorted set. # @@ -1105,12 +1151,12 @@ # # @param [String] key # @param [String] member # @return [Float] score of the member def zscore(key, member) - synchronize do - @client.call [:zscore, key, member] do |reply| + synchronize do |client| + client.call [:zscore, key, member] do |reply| Float(reply) if reply end end end @@ -1127,17 +1173,20 @@ # - `:weights => [Float, Float, ...]`: weights to associate with source # sorted sets # - `:aggregate => String`: aggregate function to use (sum, min, max, ...) # @return [Fixnum] number of elements in the resulting sorted set def zinterstore(destination, keys, options = {}) - command = CommandOptions.new(options) do |c| - c.splat :weights - c.value :aggregate - end + args = [] - synchronize do - @client.call [:zinterstore, destination, keys.size, *(keys + command.to_a)] + weights = options[:weights] + args.concat ["WEIGHTS", *weights] if weights + + aggregate = options[:aggregate] + args.concat ["AGGREGATE", aggregate] if aggregate + + synchronize do |client| + client.call [:zinterstore, destination, keys.size, *(keys + args)] end end # Add multiple sorted sets and store the resulting sorted set in a new key. # @@ -1151,17 +1200,20 @@ # - `:weights => [Float, Float, ...]`: weights to associate with source # sorted sets # - `:aggregate => String`: aggregate function to use (sum, min, max, ...) # @return [Fixnum] number of elements in the resulting sorted set def zunionstore(destination, keys, options = {}) - command = CommandOptions.new(options) do |c| - c.splat :weights - c.value :aggregate - end + args = [] - synchronize do - @client.call [:zunionstore, destination, keys.size, *(keys + command.to_a)] + weights = options[:weights] + args.concat ["WEIGHTS", *weights] if weights + + aggregate = options[:aggregate] + args.concat ["AGGREGATE", aggregate] if aggregate + + synchronize do |client| + client.call [:zunionstore, destination, keys.size, *(keys + args)] end end # Move a key to another database. # @@ -1181,136 +1233,217 @@ # # @param [String] key # @param [Fixnum] db # @return [Boolean] whether the key was moved or not def move(key, db) - synchronize do - @client.call [:move, key, db], &_boolify + synchronize do |client| + client.call [:move, key, db], &_boolify end end # Set the value of a key, only if the key does not exist. # # @param [String] key # @param [String] value # @return [Boolean] whether the key was set or not def setnx(key, value) - synchronize do - @client.call [:setnx, key, value], &_boolify + synchronize do |client| + client.call [:setnx, key, value], &_boolify end end # Delete one or more keys. # # @param [String, Array<String>] keys - # @return [Fixnum] number of keys that were removed + # @return [Fixnum] number of keys that were deleted def del(*keys) - synchronize do - @client.call [:del, *keys] + synchronize do |client| + client.call [:del, *keys] end end # Rename a key. If the new key already exists it is overwritten. # # @param [String] old_name # @param [String] new_name # @return [String] `OK` def rename(old_name, new_name) - synchronize do - @client.call [:rename, old_name, new_name] + synchronize do |client| + client.call [:rename, old_name, new_name] end end # Rename a key, only if the new key does not exist. # # @param [String] old_name # @param [String] new_name # @return [Boolean] whether the key was renamed or not def renamenx(old_name, new_name) - synchronize do - @client.call [:renamenx, old_name, new_name], &_boolify + synchronize do |client| + client.call [:renamenx, old_name, new_name], &_boolify end end # Set a key's time to live in seconds. # # @param [String] key - # @param [Fixnum] seconds time to live. After this timeout has expired, - # the key will automatically be deleted + # @param [Fixnum] seconds time to live # @return [Boolean] whether the timeout was set or not def expire(key, seconds) - synchronize do - @client.call [:expire, key, seconds], &_boolify + synchronize do |client| + client.call [:expire, key, seconds], &_boolify end end + # Set a key's time to live in milliseconds. + # + # @param [String] key + # @param [Fixnum] milliseconds time to live + # @return [Boolean] whether the timeout was set or not + def pexpire(key, milliseconds) + synchronize do |client| + client.call [:pexpire, key, milliseconds], &_boolify + end + end + # Remove the expiration from a key. # # @param [String] key # @return [Boolean] whether the timeout was removed or not def persist(key) - synchronize do - @client.call [:persist, key], &_boolify + synchronize do |client| + client.call [:persist, key], &_boolify end end - # Get the time to live for a key. + # Get the time to live (in seconds) for a key. # # @param [String] key # @return [Fixnum] remaining time to live in seconds, or -1 if the # key does not exist or does not have a timeout def ttl(key) - synchronize do - @client.call [:ttl, key] + synchronize do |client| + client.call [:ttl, key] end end + # Get the time to live (in milliseconds) for a key. + # + # @param [String] key + # @return [Fixnum] remaining time to live in milliseconds, or -1 if the + # key does not exist or does not have a timeout + def pttl(key) + synchronize do |client| + client.call [:pttl, key] + end + end + # Set the expiration for a key as a UNIX timestamp. # # @param [String] key # @param [Fixnum] unix_time expiry time specified as a UNIX timestamp - # (seconds since January 1, 1970). After this timeout has expired, - # the key will automatically be deleted # @return [Boolean] whether the timeout was set or not def expireat(key, unix_time) - synchronize do - @client.call [:expireat, key, unix_time], &_boolify + synchronize do |client| + client.call [:expireat, key, unix_time], &_boolify end end + # Set the expiration for a key as number of milliseconds from UNIX Epoch. + # + # @param [String] key + # @param [Fixnum] ms_unix_time expiry time specified as number of milliseconds from UNIX Epoch. + # @return [Boolean] whether the timeout was set or not + def pexpireat(key, ms_unix_time) + synchronize do |client| + client.call [:pexpireat, key, ms_unix_time], &_boolify + end + end # Set the string value of a hash field. + # + # @param [String] key + # @param [String] field + # @param [String] value + # @return [Boolean] whether or not the field was **added** to the hash def hset(key, field, value) - synchronize do - @client.call [:hset, key, field, value], &_boolify + synchronize do |client| + client.call [:hset, key, field, value], &_boolify end end # Set the value of a hash field, only if the field does not exist. + # + # @param [String] key + # @param [String] field + # @param [String] value + # @return [Boolean] whether or not the field was **added** to the hash def hsetnx(key, field, value) - synchronize do - @client.call [:hsetnx, key, field, value], &_boolify + synchronize do |client| + client.call [:hsetnx, key, field, value], &_boolify end end - # Set multiple hash fields to multiple values. + # Set one or more hash values. + # + # @example + # redis.hmset("hash", "f1", "v1", "f2", "v2") + # # => "OK" + # + # @param [String] key + # @param [Array<String>] attrs array of fields and values + # @return `"OK"` + # + # @see #mapped_hmset def hmset(key, *attrs) - synchronize do - @client.call [:hmset, key, *attrs] + synchronize do |client| + client.call [:hmset, key, *attrs] end end + # Set one or more hash values. + # + # @example + # redis.hmset("hash", { "f1" => "v1", "f2" => "v2" }) + # # => "OK" + # + # @param [String] key + # @param [Hash] hash fields mapping to values + # @return `"OK"` + # + # @see #hmset def mapped_hmset(key, hash) hmset(key, *hash.to_a.flatten) end # Get the values of all the given hash fields. + # + # @example + # redis.hmget("hash", "f1", "f2") + # # => ["v1", "v2"] + # + # @param [String] key + # @param [Array<String>] fields array of fields + # @return [Array<String>] an array of values for the specified fields + # + # @see #mapped_hmget def hmget(key, *fields, &blk) - synchronize do - @client.call [:hmget, key, *fields], &blk + synchronize do |client| + client.call [:hmget, key, *fields], &blk end end + # Get the values of all the given hash fields. + # + # @example + # redis.hmget("hash", "f1", "f2") + # # => { "f1" => "v1", "f2" => "v2" } + # + # @param [String] key + # @param [Array<String>] fields array of fields + # @return [Hash] a hash mapping the specified fields to their values + # + # @see #hmget def mapped_hmget(key, *fields) hmget(key, *fields) do |reply| if reply.kind_of?(Array) hash = Hash.new fields.zip(reply).each do |field, value| @@ -1322,122 +1455,228 @@ end end end # Get the number of fields in a hash. + # + # @param [String] key + # @return [Fixnum] number of fields in the hash def hlen(key) - synchronize do - @client.call [:hlen, key] + synchronize do |client| + client.call [:hlen, key] end end # Get all the values in a hash. + # + # @param [String] key + # @return [Array<String>] def hvals(key) - synchronize do - @client.call [:hvals, key] + synchronize do |client| + client.call [:hvals, key] end end - # Increment the integer value of a hash field by the given number. + # Increment the integer value of a hash field by the given integer number. + # + # @param [String] key + # @param [String] field + # @param [Fixnum] increment + # @return [Fixnum] value of the field after incrementing it def hincrby(key, field, increment) - synchronize do - @client.call [:hincrby, key, field, increment] + synchronize do |client| + client.call [:hincrby, key, field, increment] end end - # Discard all commands issued after MULTI. - def discard - synchronize do - @client.call [:discard] + # Increment the numeric value of a hash field by the given float number. + # + # @param [String] key + # @param [String] field + # @param [Float] increment + # @return [Float] value of the field after incrementing it + def hincrbyfloat(key, field, increment) + synchronize do |client| + client.call [:hincrbyfloat, key, field, increment] do |reply| + Float(reply) if reply + end end end # Determine if a hash field exists. + # + # @param [String] key + # @param [String] field + # @return [Boolean] whether or not the field exists in the hash def hexists(key, field) - synchronize do - @client.call [:hexists, key, field], &_boolify + synchronize do |client| + client.call [:hexists, key, field], &_boolify end end # Listen for all requests received by the server in real time. + # + # There is no way to interrupt this command. + # + # @yield a block to be called for every line of output + # @yieldparam [String] line timestamp and command that was executed def monitor(&block) - synchronize do - @client.call_loop([:monitor], &block) + synchronize do |client| + client.call_loop([:monitor], &block) end end def debug(*args) - synchronize do - @client.call [:debug, *args] + synchronize do |client| + client.call [:debug, *args] end end def object(*args) - synchronize do - @client.call [:object, *args] + synchronize do |client| + client.call [:object, *args] end end # Internal command used for replication. def sync - synchronize do - @client.call [:sync] + synchronize do |client| + client.call [:sync] end end # Set the string value of a key. + # + # @param [String] key + # @param [String] value + # @return `"OK"` def set(key, value) - synchronize do - @client.call [:set, key, value] + synchronize do |client| + client.call [:set, key, value] end end alias :[]= :set # Sets or clears the bit at offset in the string value stored at key. + # + # @param [String] key + # @param [Fixnum] offset bit offset + # @param [Fixnum] value bit value `0` or `1` + # @return [Fixnum] the original bit value stored at `offset` def setbit(key, offset, value) - synchronize do - @client.call [:setbit, key, offset, value] + synchronize do |client| + client.call [:setbit, key, offset, value] end end - # Set the value and expiration of a key. + # Set the time to live in seconds of a key. + # + # @param [String] key + # @param [Fixnum] ttl + # @param [String] value + # @return `"OK"` def setex(key, ttl, value) - synchronize do - @client.call [:setex, key, ttl, value] + synchronize do |client| + client.call [:setex, key, ttl, value] end end + # Set the time to live in milliseconds of a key. + # + # @param [String] key + # @param [Fixnum] ttl + # @param [String] value + # @return `"OK"` + def psetex(key, ttl, value) + synchronize do |client| + client.call [:psetex, key, ttl, value] + end + end + # Overwrite part of a string at key starting at the specified offset. + # + # @param [String] key + # @param [Fixnum] offset byte offset + # @param [String] value + # @return [Fixnum] length of the string after it was modified def setrange(key, offset, value) - synchronize do - @client.call [:setrange, key, offset, value] + synchronize do |client| + client.call [:setrange, key, offset, value] end end - # Set multiple keys to multiple values. + # Set one or more values. + # + # @example + # redis.mset("key1", "v1", "key2", "v2") + # # => "OK" + # + # @param [Array<String>] args array of keys and values + # @return `"OK"` + # + # @see #mapped_mset def mset(*args) - synchronize do - @client.call [:mset, *args] + synchronize do |client| + client.call [:mset, *args] end end + # Set one or more values. + # + # @example + # redis.mapped_mset({ "f1" => "v1", "f2" => "v2" }) + # # => "OK" + # + # @param [Hash] hash keys mapping to values + # @return `"OK"` + # + # @see #mset def mapped_mset(hash) mset(*hash.to_a.flatten) end - # Set multiple keys to multiple values, only if none of the keys exist. + # Set one or more values, only if none of the keys exist. + # + # @example + # redis.msetnx("key1", "v1", "key2", "v2") + # # => true + # + # @param [Array<String>] args array of keys and values + # @return [Boolean] whether or not all values were set + # + # @see #mapped_msetnx def msetnx(*args) - synchronize do - @client.call [:msetnx, *args] + synchronize do |client| + client.call [:msetnx, *args], &_boolify end end + # Set one or more values, only if none of the keys exist. + # + # @example + # redis.msetnx({ "key1" => "v1", "key2" => "v2" }) + # # => true + # + # @param [Hash] hash keys mapping to values + # @return [Boolean] whether or not all values were set + # + # @see #msetnx def mapped_msetnx(hash) msetnx(*hash.to_a.flatten) end + # Get the values of all the given keys. + # + # @example + # redis.mapped_mget("key1", "key1") + # # => { "key1" => "v1", "key2" => "v2" } + # + # @param [Array<String>] keys array of keys + # @return [Hash] a hash mapping the specified keys to their values + # + # @see #mget def mapped_mget(*keys) mget(*keys) do |reply| if reply.kind_of?(Array) hash = Hash.new keys.zip(reply).each do |field, value| @@ -1449,133 +1688,290 @@ end end end # Sort the elements in a list, set or sorted set. + # + # @example Retrieve the first 2 elements from an alphabetically sorted "list" + # redis.sort("list", :order => "alpha", :limit => [0, 2]) + # # => ["a", "b"] + # @example Store an alphabetically descending list in "target" + # redis.sort("list", :order => "desc alpha", :store => "target") + # # => 26 + # + # @param [String] key + # @param [Hash] options + # - `:by => String`: use external key to sort elements by + # - `:limit => [offset, count]`: skip `offset` elements, return a maximum + # of `count` elements + # - `:get => [String, Array<String>]`: single key or array of keys to + # retrieve per element in the result + # - `:order => String`: combination of `ASC`, `DESC` and optionally `ALPHA` + # - `:store => String`: key to store the result at + # + # @return [Array<String>, Array<Array<String>>, Fixnum] + # - when `:get` is not specified, or holds a single element, an array of elements + # - when `:get` is specified, and holds more than one element, an array of + # elements where every element is an array with the result for every + # element specified in `:get` + # - when `:store` is specified, the number of elements in the stored result def sort(key, options = {}) - command = CommandOptions.new(options) do |c| - c.value :by - c.splat :limit - c.multi :get - c.words :order - c.value :store - end + args = [] - synchronize do - @client.call [:sort, key, *command.to_a] + by = options[:by] + args.concat ["BY", by] if by + + limit = options[:limit] + args.concat ["LIMIT", *limit] if limit + + get = Array(options[:get]) + args.concat ["GET"].product(get).flatten unless get.empty? + + order = options[:order] + args.concat order.split(" ") if order + + store = options[:store] + args.concat ["STORE", store] if store + + synchronize do |client| + client.call [:sort, key, *args] do |reply| + if get.size > 1 + if reply + reply.each_slice(get.size).to_a + end + else + reply + end + end end end # Increment the integer value of a key by one. + # + # @example + # redis.incr("value") + # # => 6 + # + # @param [String] key + # @return [Fixnum] value after incrementing it def incr(key) - synchronize do - @client.call [:incr, key] + synchronize do |client| + client.call [:incr, key] end end - # Increment the integer value of a key by the given number. + # Increment the integer value of a key by the given integer number. + # + # @example + # redis.incrby("value", 5) + # # => 10 + # + # @param [String] key + # @param [Fixnum] increment + # @return [Fixnum] value after incrementing it def incrby(key, increment) - synchronize do - @client.call [:incrby, key, increment] + synchronize do |client| + client.call [:incrby, key, increment] end end + # Increment the numeric value of a key by the given float number. + # + # @example + # redis.incrbyfloat("value", 1.23) + # # => 1.23 + # + # @param [String] key + # @param [Float] increment + # @return [Float] value after incrementing it + def incrbyfloat(key, increment) + synchronize do |client| + client.call [:incrbyfloat, key, increment] do |reply| + Float(reply) if reply + end + end + end + # Decrement the integer value of a key by one. + # + # @example + # redis.decr("value") + # # => 4 + # + # @param [String] key + # @return [Fixnum] value after decrementing it def decr(key) - synchronize do - @client.call [:decr, key] + synchronize do |client| + client.call [:decr, key] end end # Decrement the integer value of a key by the given number. + # + # @example + # redis.decrby("value", 5) + # # => 0 + # + # @param [String] key + # @param [Fixnum] decrement + # @return [Fixnum] value after decrementing it def decrby(key, decrement) - synchronize do - @client.call [:decrby, key, decrement] + synchronize do |client| + client.call [:decrby, key, decrement] end end # Determine the type stored at key. # # @param [String] key # @return [String] `string`, `list`, `set`, `zset`, `hash` or `none` def type(key) - synchronize do - @client.call [:type, key] + synchronize do |client| + client.call [:type, key] end end # Close the connection. + # + # @return [String] `OK` def quit - synchronize do + synchronize do |client| begin - @client.call [:quit] + client.call [:quit] rescue ConnectionError ensure - @client.disconnect + client.disconnect end end end # Synchronously save the dataset to disk and then shut down the server. def shutdown - synchronize do - @client.without_reconnect do + synchronize do |client| + client.without_reconnect do begin - @client.call [:shutdown] + client.call [:shutdown] rescue ConnectionError # This means Redis has probably exited. nil end end end end # Make the server a slave of another instance, or promote it as master. def slaveof(host, port) - synchronize do - @client.call [:slaveof, host, port] + synchronize do |client| + client.call [:slaveof, host, port] end end def pipelined - synchronize do + synchronize do |client| begin original, @client = @client, Pipeline.new - yield + yield(self) original.call_pipeline(@client) ensure @client = original end end end # Watch the given keys to determine execution of the MULTI/EXEC block. + # + # Using a block is optional, but is necessary for thread-safety. + # + # An `#unwatch` is automatically issued if an exception is raised within the + # block that is a subclass of StandardError and is not a ConnectionError. + # + # @example With a block + # redis.watch("key") do + # if redis.get("key") == "some value" + # redis.multi do |multi| + # multi.set("key", "other value") + # multi.incr("counter") + # end + # else + # redis.unwatch + # end + # end + # # => ["OK", 6] + # + # @example Without a block + # redis.watch("key") + # # => "OK" + # + # @param [String, Array<String>] keys one or more keys to watch + # @return [Object] if using a block, returns the return value of the block + # @return [String] if not using a block, returns `OK` + # + # @see #unwatch + # @see #multi def watch(*keys) - synchronize do - @client.call [:watch, *keys] + synchronize do |client| + client.call [:watch, *keys] + + if block_given? + begin + yield + rescue ConnectionError + raise + rescue StandardError + unwatch + raise + end + end end end # Forget about all watched keys. + # + # @return [String] `OK` + # + # @see #watch + # @see #multi def unwatch - synchronize do - @client.call [:unwatch] + synchronize do |client| + client.call [:unwatch] end end - # Execute all commands issued after MULTI. - def exec - synchronize do - @client.call [:exec] - end - end - # Mark the start of a transaction block. + # + # Passing a block is optional. + # + # @example With a block + # redis.multi do |multi| + # multi.set("key", "value") + # multi.incr("counter") + # end # => ["OK", 6] + # + # @example Without a block + # redis.multi + # # => "OK" + # redis.set("key", "value") + # # => "QUEUED" + # redis.incr("counter") + # # => "QUEUED" + # redis.exec + # # => ["OK", 6] + # + # @yield [multi] the commands that are called inside this block are cached + # and written to the server upon returning from it + # @yieldparam [Redis] multi `self` + # + # @return [String, Array<...>] + # - when a block is not given, `OK` + # - when a block is given, an array with replies + # + # @see #watch + # @see #unwatch def multi - synchronize do + synchronize do |client| if !block_given? - @client.call [:multi] + client.call [:multi] else begin pipeline = Pipeline::Multi.new original, @client = @client, pipeline yield(self) @@ -1585,107 +1981,101 @@ end end end end + # Execute all commands issued after MULTI. + # + # Only call this method when `#multi` was called **without** a block. + # + # @return [nil, Array<...>] + # - when commands were not executed, `nil` + # - when commands were executed, an array with their replies + # + # @see #multi + # @see #discard + def exec + synchronize do |client| + client.call [:exec] + end + end + + # Discard all commands issued after MULTI. + # + # Only call this method when `#multi` was called **without** a block. + # + # @return `"OK"` + # + # @see #multi + # @see #exec + def discard + synchronize do |client| + client.call [:discard] + end + end + # Post a message to a channel. def publish(channel, message) - synchronize do - @client.call [:publish, channel, message] + synchronize do |client| + client.call [:publish, channel, message] end end def subscribed? - synchronize do - @client.kind_of? SubscribedClient + synchronize do |client| + client.kind_of? SubscribedClient end end # Stop listening for messages posted to the given channels. def unsubscribe(*channels) - synchronize do + synchronize do |client| raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? - @client.unsubscribe(*channels) + client.unsubscribe(*channels) end end # Stop listening for messages posted to channels matching the given patterns. def punsubscribe(*channels) - synchronize do + synchronize do |client| raise RuntimeError, "Can't unsubscribe if not subscribed." unless subscribed? - @client.punsubscribe(*channels) + client.punsubscribe(*channels) end end # Listen for messages published to the given channels. def subscribe(*channels, &block) - synchronize do - subscription(:subscribe, channels, block) + synchronize do |client| + _subscription(:subscribe, channels, block) end end # Listen for messages published to channels matching the given patterns. def psubscribe(*channels, &block) - synchronize do - subscription(:psubscribe, channels, block) + synchronize do |client| + _subscription(:psubscribe, channels, block) end end def id - synchronize do - @client.id + synchronize do |client| + client.id end end def inspect - synchronize do - "#<Redis client v#{Redis::VERSION} connected to #{id} (Redis v#{info["redis_version"]})>" + synchronize do |client| + "#<Redis client v#{Redis::VERSION} for #{client.id}>" end end def method_missing(command, *args) - synchronize do - @client.call [command, *args] + synchronize do |client| + client.call [command, *args] end end - class CommandOptions - def initialize(options) - @result = [] - @options = options - yield(self) - end - - def bool(name) - insert(name) { |argument, value| [argument] } - end - - def value(name) - insert(name) { |argument, value| [argument, value] } - end - - def splat(name) - insert(name) { |argument, value| [argument, *value] } - end - - def multi(name) - insert(name) { |argument, value| [argument].product(Array(value)).flatten } - end - - def words(name) - insert(name) { |argument, value| value.split(" ") } - end - - def to_a - @result - end - - def insert(name) - @result += yield(name.to_s.upcase.gsub("_", ""), @options[name]) if @options[name] - end - end - private # Commands returning 1 for true and 0 for false may be executed in a pipeline # where the method call will return nil. Propagate the nil instead of falsely # returning false. @@ -1693,10 +2083,20 @@ lambda { |value| value == 1 if value } end - def subscription(method, channels, block) + def _hashify + lambda { |array| + hash = Hash.new + array.each_slice(2) do |field, value| + hash[field] = value + end + hash + } + end + + def _subscription(method, channels, block) return @client.call [method, *channels] if subscribed? begin original, @client = @client, SubscribedClient.new(@client) @client.send(method, *channels, &block)