summary refs log tree commit diff stats
path: root/lib/pure
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure')
-rw-r--r--lib/pure/redis.nim392
1 files changed, 217 insertions, 175 deletions
diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim
index 8012af9eb..e4a2b04e7 100644
--- a/lib/pure/redis.nim
+++ b/lib/pure/redis.nim
@@ -9,8 +9,9 @@
 
 ## This module implements a redis client. It allows you to connect to a redis-server instance, send commands and receive replies.
 ##
-## **Beware**: Most (if not all) functions that return a `TRedisString` may
-## return `redisNil`.
+## **Beware**: Most (if not all) functions that return a ``TRedisString`` may
+## return ``redisNil``, and functions which return a ``TRedisList`` 
+## may return ``nil``.
 
 import sockets, os, strutils, parseutils
 
@@ -37,17 +38,6 @@ proc open*(host = "localhost", port = 6379.TPort): TRedis =
     OSError()
   result.socket.connect(host, port)
 
-proc stripNewline(s: string): string =
-  ## Strips trailing new line
-  const
-    chars: set[Char] = {'\c', '\L'}
-    first = 0
-    
-  var last = len(s)-1
-  
-  while last >= 0 and s[last] in chars: dec(last)
-  result = copy(s, first, last)
-
 proc raiseInvalidReply(expected, got: char) =
   raise newException(EInvalidReply, 
           "Expected '$1' at the beginning of a status reply got '$2'" %
@@ -61,7 +51,7 @@ proc parseStatus(r: TRedis): TRedisStatus =
   var line = r.socket.recv()
   
   if line[0] == '-':
-    raise newException(ERedis, stripNewline(line))
+    raise newException(ERedis, strip(line))
   if line[0] != '+':
     raiseInvalidReply('+', line[0])
   
@@ -69,13 +59,15 @@ proc parseStatus(r: TRedis): TRedisStatus =
   
 proc parseInteger(r: TRedis): TRedisInteger =
   var line = r.socket.recv()
-  
+
   if line[0] == '-':
-    raise newException(ERedis, stripNewline(line))
+    raise newException(ERedis, strip(line))
   if line[0] != ':':
     raiseInvalidReply(':', line[0])
   
-  return parseBiggestInt(line, result, 1) # Strip ':' and \c\L.
+  # Strip ':' and \c\L.
+  if parseBiggestInt(line, result, 1) == 0:
+    raise newException(EInvalidReply, "Unable to parse integer.") 
 
 proc recv(sock: TSocket, size: int): string =
   result = newString(size)
@@ -89,23 +81,22 @@ proc parseBulk(r: TRedis, allowMBNil = False): TRedisString =
   
   # Error.
   if line[0] == '-':
-    raise newException(ERedis, stripNewline(line))
+    raise newException(ERedis, strip(line))
   
   # Some commands return a /bulk/ value or a /multi-bulk/ nil. Odd.
   if allowMBNil:
     if line == "*-1":
-       result = RedisNil
-       return
+       return RedisNil
   
   if line[0] != '$':
     raiseInvalidReply('$', line[0])
   
   var numBytes = parseInt(line.copy(1))
   if numBytes == -1:
-    result = RedisNil
-    return
+    return RedisNil
+
   var s = r.socket.recv(numBytes+2)
-  result = stripNewline(s)
+  result = strip(s)
 
 proc parseMultiBulk(r: TRedis): TRedisList =
   var line = ""
@@ -121,72 +112,93 @@ proc parseMultiBulk(r: TRedis): TRedisList =
   for i in 1..numElems:
     result.add(r.parseBulk())
 
+proc sendCommand(r: TRedis, cmd: string, args: openarray[string]) =
+  var request = "*" & $(1 + args.len()) & "\c\L"
+  request.add("$" & $cmd.len() & "\c\L")
+  request.add(cmd & "\c\L")
+  for i in items(args):
+    request.add("$" & $i.len() & "\c\L")
+    request.add(i & "\c\L")
+  r.socket.send(request)
+
+proc sendCommand(r: TRedis, cmd: string, arg1: string,
+                 args: openarray[string]) =
+  var request = "*" & $(2 + args.len()) & "\c\L"
+  request.add("$" & $cmd.len() & "\c\L")
+  request.add(cmd & "\c\L")
+  request.add("$" & $arg1.len() & "\c\L")
+  request.add(arg1 & "\c\L")
+  for i in items(args):
+    request.add("$" & $i.len() & "\c\L")
+    request.add(i & "\c\L")
+  r.socket.send(request)
+
 # Keys
 
 proc del*(r: TRedis, keys: openArray[string]): TRedisInteger =
   ## Delete a key or multiple keys
-  r.socket.send("DEL $1\c\L" % keys.join(" "))
+  r.sendCommand("DEL", keys)
   return r.parseInteger()
 
 proc exists*(r: TRedis, key: string): bool =
   ## Determine if a key exists
-  r.socket.send("EXISTS $1\c\L" % key)
+  r.sendCommand("EXISTS", key)
   return r.parseInteger() == 1
 
 proc expire*(r: TRedis, key: string, seconds: int): bool =
   ## Set a key's time to live in seconds. Returns `false` if the key could
   ## not be found or the timeout could not be set.
-  r.socket.send("EXPIRE $1 $2\c\L" % [key, $seconds])
+  r.sendCommand("EXPIRE", key, $seconds)
   return r.parseInteger() == 1
 
 proc expireAt*(r: TRedis, key: string, timestamp: int): bool =
   ## Set the expiration for a key as a UNIX timestamp. Returns `false` 
   ## if the key could not be found or the timeout could not be set.
-  r.socket.send("EXPIREAT $1 $2\c\L" % [key, $timestamp])
+  r.sendCommand("EXPIREAT", key, $timestamp)
   return r.parseInteger() == 1
 
 proc keys*(r: TRedis, pattern: string): TRedisList =
   ## Find all keys matching the given pattern
-  r.socket.send("KEYS $1\c\L" % pattern)
+  r.sendCommand("KEYS", pattern)
   return r.parseMultiBulk()
 
 proc move*(r: TRedis, key: string, db: int): bool =
   ## Move a key to another database. Returns `true` on a successful move.
-  r.socket.send("MOVE $1 $2\c\L" % [key, $db])
+  r.sendCommand("MOVE", key, $db)
   return r.parseInteger() == 1
 
 proc persist*(r: TRedis, key: string): bool =
   ## Remove the expiration from a key. 
   ## Returns `true` when the timeout was removed.
-  r.socket.send("PERSIST $1\c\L" % key)
+  r.sendCommand("PERSIST", key)
   return r.parseInteger() == 1
   
 proc randomKey*(r: TRedis): TRedisString =
   ## Return a random key from the keyspace
-  r.socket.send("RANDOMKEY\c\L")
+  r.sendCommand("RANDOMKEY")
   return r.parseBulk()
 
 proc rename*(r: TRedis, key, newkey: string): TRedisStatus =
   ## Rename a key.
   ## 
   ## **WARNING:** Overwrites `newkey` if it exists!
-  r.socket.send("RENAME $1 $2\c\L" % [key, newkey])
+  r.sendCommand("RENAME", key, newkey)
   raiseNoOK(r.parseStatus())
   
 proc renameNX*(r: TRedis, key, newkey: string): bool =
   ## Same as ``rename`` but doesn't continue if `newkey` exists.
   ## Returns `true` if key was renamed.
-  r.socket.send("RENAMENX $1 $2\c\L" % [key, newkey])
+  r.sendCommand("RENAMENX", key, newkey)
   return r.parseInteger() == 1
 
 proc ttl*(r: TRedis, key: string): TRedisInteger =
   ## Get the time to live for a key
-  r.socket.send("TTL $1\c\L" % key)
+  r.sendCommand("TTL", key)
   return r.parseInteger()
   
 proc keyType*(r: TRedis, key: string): TRedisStatus =
   ## Determine the type stored at key
-  r.socket.send("TYPE $1\c\L" % key)
+  r.sendCommand("TYPE", key)
   return r.parseStatus()
   
 
@@ -194,149 +206,150 @@ proc keyType*(r: TRedis, key: string): TRedisStatus =
 
 proc append*(r: TRedis, key, value: string): TRedisInteger =
   ## Append a value to a key
-  r.socket.send("APPEND $1 \"$2\"\c\L" % [key, value])
+  r.sendCommand("APPEND", key, value)
   return r.parseInteger()
 
 proc decr*(r: TRedis, key: string): TRedisInteger =
   ## Decrement the integer value of a key by one
-  r.socket.send("DECR $1\c\L" % key)
+  r.sendCommand("DECR", key)
   return r.parseInteger()
   
 proc decrBy*(r: TRedis, key: string, decrement: int): TRedisInteger =
   ## Decrement the integer value of a key by the given number
-  r.socket.send("DECRBY $1 $2\c\L" % [key, $decrement])
+  r.sendCommand("DECRBY", key, $decrement)
   return r.parseInteger()
   
 proc get*(r: TRedis, key: string): TRedisString =
-  ## Get the value of a key. Returns `nil` when `key` doesn't exist.
-  r.socket.send("GET $1\c\L" % key)
+  ## Get the value of a key. Returns `redisNil` when `key` doesn't exist.
+  r.sendCommand("GET", key)
   return r.parseBulk()
 
 proc getBit*(r: TRedis, key: string, offset: int): TRedisInteger =
   ## Returns the bit value at offset in the string value stored at key
-  r.socket.send("GETBIT $1 $2\c\L" % [key, $offset])
+  r.sendCommand("GETBIT", key, $offset)
   return r.parseInteger()
 
 proc getRange*(r: TRedis, key: string, start, stop: int): TRedisString =
   ## Get a substring of the string stored at a key
-  r.socket.send("GETRANGE $1 $2 $3\c\L" % [key, $start, $stop])
+  r.sendCommand("GETRANGE", key, $start, $stop)
   return r.parseBulk()
 
 proc getSet*(r: TRedis, key: string, value: string): TRedisString =
-  ## Set the string value of a key and return its old value. Returns `nil` when
-  ## key doesn't exist.
-  r.socket.send("GETSET $1 \"$2\"\c\L" % [key, value])
+  ## Set the string value of a key and return its old value. Returns `redisNil`
+  ## when key doesn't exist.
+  r.sendCommand("GETSET", key, value)
   return r.parseBulk()
 
 proc incr*(r: TRedis, key: string): TRedisInteger =
   ## Increment the integer value of a key by one.
-  r.socket.send("INCR $1\c\L" % key)
+  r.sendCommand("INCR", key)
   return r.parseInteger()
 
 proc incrBy*(r: TRedis, key: string, increment: int): TRedisInteger =
   ## Increment the integer value of a key by the given number
-  r.socket.send("INCRBY $1 $2\c\L" % [key, $increment])
+  r.sendCommand("INCRBY", key, $increment)
   return r.parseInteger()
 
 proc setk*(r: TRedis, key, value: string) = 
   ## Set the string value of a key.
   ##
   ## NOTE: This function had to be renamed due to a clash with the `set` type.
-  r.socket.send("SET $1 \"$2\"\c\L" % [key, value])
+  r.sendCommand("SET", key, value)
   raiseNoOK(r.parseStatus())
 
 proc setNX*(r: TRedis, key, value: string): bool =
   ## Set the value of a key, only if the key does not exist. Returns `true`
   ## if the key was set.
-  r.socket.send("SETNX $1 \"$2\"\c\L" % [key, value])
+  r.sendCommand("SETNX", key, value)
   return r.parseInteger() == 1
 
 proc setBit*(r: TRedis, key: string, offset: int, 
-  value: string): TRedisInteger =
+             value: string): TRedisInteger =
   ## Sets or clears the bit at offset in the string value stored at key
-  r.socket.send("SETBIT $1 $2 \"$3\"\c\L" % [key, $offset, value])
+  r.sendCommand("SETBIT", key, $offset, value)
   return r.parseInteger()
   
 proc setEx*(r: TRedis, key: string, seconds: int, value: string): TRedisStatus =
   ## Set the value and expiration of a key
-  r.socket.send("SETEX $1 $2 \"$3\"\c\L" % [key, $seconds, value])
+  r.sendCommand("SETEX", key, $seconds, value)
   raiseNoOK(r.parseStatus())
 
 proc setRange*(r: TRedis, key: string, offset: int, 
-  value: string): TRedisInteger =
+               value: string): TRedisInteger =
   ## Overwrite part of a string at key starting at the specified offset
-  r.socket.send("SETRANGE $1 $2 \"$3\"\c\L" % [key, $offset, value])
+  r.sendCommand("SETRANGE", key, $offset, value)
   return r.parseInteger()
 
 proc strlen*(r: TRedis, key: string): TRedisInteger =
   ## Get the length of the value stored in a key. Returns 0 when key doesn't
   ## exist.
-  r.socket.send("STRLEN $1\c\L" % key)
+  r.sendCommand("STRLEN", key)
   return r.parseInteger()
 
 # Hashes
 proc hDel*(r: TRedis, key, field: string): bool =
-  ## Delete a hash field at `key`. Returns `true` if field was removed.
-  r.socket.send("HDEL $1 $2\c\L" % [key, field])
+  ## Delete a hash field at `key`. Returns `true` if the field was removed.
+  r.sendCommand("HDEL", key, field)
   return r.parseInteger() == 1
 
 proc hExists*(r: TRedis, key, field: string): bool =
   ## Determine if a hash field exists.
-  r.socket.send("HEXISTS $1 $2\c\L" % [key, field])
+  r.sendCommand("HEXISTS", key, field)
   return r.parseInteger() == 1
 
 proc hGet*(r: TRedis, key, field: string): TRedisString =
   ## Get the value of a hash field
-  r.socket.send("HGET $1 $2\c\L" % [key, field])
+  r.sendCommand("HGET", key, field)
   return r.parseBulk()
 
 proc hGetAll*(r: TRedis, key: string): TRedisList =
   ## Get all the fields and values in a hash
-  r.socket.send("HGETALL $1\c\L" % key)
+  r.sendCommand("HGETALL", key)
   return r.parseMultiBulk()
 
 proc hIncrBy*(r: TRedis, key, field: string, incr: int): TRedisInteger =
   ## Increment the integer value of a hash field by the given number
-  r.socket.send("HINCRBY $1 $2 $3\c\L" % [key, field, $incr])
+  r.sendCommand("HINCRBY", key, field, $incr)
   return r.parseInteger()
 
 proc hKeys*(r: TRedis, key: string): TRedisList =
   ## Get all the fields in a hash
-  r.socket.send("HKEYS $1\c\L" % key)
+  r.sendCommand("HKEYS", key)
   return r.parseMultiBulk()
 
 proc hLen*(r: TRedis, key: string): TRedisInteger =
   ## Get the number of fields in a hash
-  r.socket.send("HLEN $1\c\L" % key)
+  r.sendCommand("HLEN", key)
   return r.parseInteger()
 
 proc hMGet*(r: TRedis, key: string, fields: openarray[string]): TRedisList =
   ## Get the values of all the given hash fields
-  r.socket.send("HMGET $1 $2\c\L" % [key, fields.join()])
+  r.sendCommand("HMGET", key, fields)
   return r.parseMultiBulk()
 
 proc hMSet*(r: TRedis, key: string, 
             fieldValues: openarray[tuple[field, value: string]]) =
   ## Set multiple hash fields to multiple values
-  var fieldVals = ""
+  var args = @[key]
   for field, value in items(fieldValues):
-    fieldVals.add(field & " " & value)
-  r.socket.send("HMSET $1 $2\c\L" % [key, fieldVals])
+    args.add(field)
+    args.add(value)
+  r.sendCommand("HMSET", args)
   raiseNoOK(r.parseStatus())
 
 proc hSet*(r: TRedis, key, field, value: string) =
   ## Set the string value of a hash field
-  r.socket.send("HSET $1 $2 \"$3\"\c\L" % [key, field, value])
+  r.sendCommand("HSET", key, field, value)
   raiseNoOK(r.parseStatus())
   
 proc hSetNX*(r: TRedis, key, field, value: string) =
   ## Set the value of a hash field, only if the field does **not** exist
-  r.socket.send("HSETNX $1 $2 \"$3\"\c\L" % [key, field, value])
+  r.sendCommand("HSETNX", key, field, value)
   raiseNoOK(r.parseStatus())
 
 proc hVals*(r: TRedis, key: string): TRedisList =
   ## Get all the values in a hash
-  r.socket.send("HVALS $1\c\L" % key)
+  r.sendCommand("HVALS", key)
   return r.parseMultiBulk()
   
 # Lists
@@ -344,13 +357,19 @@ proc hVals*(r: TRedis, key: string): TRedisList =
 proc bLPop*(r: TRedis, keys: openarray[string], timeout: int): TRedisList =
   ## Remove and get the *first* element in a list, or block until 
   ## one is available
-  r.socket.send("BLPOP $1 $2\c\L" % [keys.join(), $timeout])
+  var args: seq[string] = @[]
+  for i in items(keys): args.add(i)
+  args.add($timeout)
+  r.sendCommand("BLPOP", args)
   return r.parseMultiBulk()
 
 proc bRPop*(r: TRedis, keys: openarray[string], timeout: int): TRedisList =
   ## Remove and get the *last* element in a list, or block until one 
   ## is available.
-  r.socket.send("BRPOP $1 $2\c\L" % [keys.join(), $timeout])
+  var args: seq[string] = @[]
+  for i in items(keys): args.add(i)
+  args.add($timeout)
+  r.sendCommand("BRPOP", args)
   return r.parseMultiBulk()
 
 proc bRPopLPush*(r: TRedis, source, destination: string,
@@ -359,196 +378,199 @@ proc bRPopLPush*(r: TRedis, source, destination: string,
   ## block until one is available.
   ##
   ## http://redis.io/commands/brpoplpush
-  r.socket.send("BRPOPLPUSH $1 $2 $3\c\L" % [source, destination, $timeout])
+  r.sendCommand("BRPOPLPUSH", source, destination, $timeout)
   return r.parseBulk(true) # Multi-Bulk nil allowed.
 
 proc lIndex*(r: TRedis, key: string, index: int): TRedisString =
   ## Get an element from a list by its index
-  r.socket.send("LINDEX $1 $2\c\L" % [key, $index])
+  r.sendCommand("LINDEX", key, $index)
   return r.parseBulk()
 
 proc lInsert*(r: TRedis, key: string, before: bool, pivot, value: string):
               TRedisInteger =
   ## Insert an element before or after another element in a list
   var pos = if before: "BEFORE" else: "AFTER"
-  r.socket.send("LINSERT $1 $2 $3 \"$4\"\c\L" % [key, pos, pivot, value])
+  r.sendCommand("LINSERT", key, pos, pivot, value)
   return r.parseInteger()
   
 proc lLen*(r: TRedis, key: string): TRedisInteger =
   ## Get the length of a list
-  r.socket.send("LLEN $1\c\L" % key)
+  r.sendCommand("LLEN", key)
   return r.parseInteger()
 
 proc lPop*(r: TRedis, key: string): TRedisString =
   ## Remove and get the first element in a list
-  r.socket.send("LPOP $1\c\L" % key)
+  r.sendCommand("LPOP", key)
   return r.parseBulk()
 
 proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger =
   ## Prepend a value to a list. Returns the length of the list after the push.
   ## The ``create`` param specifies whether a list should be created if it
-  ## doesn't exist at ``key``. More specifically if ``create`` is True, `LPUSH` will
-  ## be used, otherwise `LPUSHX`.
+  ## doesn't exist at ``key``. More specifically if ``create`` is True, `LPUSH` 
+  ## will be used, otherwise `LPUSHX`.
   if create:
-    r.socket.send("LPUSH $1 \"$2\"\c\L" % [key, value])
+    r.sendCommand("LPUSH", key, value)
   else:
-    r.socket.send("LPUSHX $1 \"$2\"\c\L" % [key, value])
+    r.sendCommand("LPUSHX", key, value)
   return r.parseInteger()
 
 proc lRange*(r: TRedis, key: string, start, stop: int): TRedisList =
   ## Get a range of elements from a list. Returns `nil` when `key` 
   ## doesn't exist.
-  r.socket.send("LRANGE $1 $2 $3\c\L" % [key, $start, $stop])
+  r.sendCommand("LRANGE", key, $start, $stop)
   return r.parseMultiBulk()
 
 proc lRem*(r: TRedis, key: string, value: string, count: int = 0): TRedisInteger =
   ## Remove elements from a list. Returns the number of elements that have been
   ## removed.
-  r.socket.send("LREM $1 $2 \"$3\"\c\L" % [key, $count, value])
+  r.sendCommand("LREM", key, $count, value)
   return r.parseInteger()
 
 proc lSet*(r: TRedis, key: string, index: int, value: string) =
   ## Set the value of an element in a list by its index
-  r.socket.send("LSET $1 $2 \"$3\"\c\L" % [key, $index, value])
+  r.sendCommand("LSET", key, $index, value)
   raiseNoOK(r.parseStatus())
 
 proc lTrim*(r: TRedis, key: string, start, stop: int) =
   ## Trim a list to the specified range
-  r.socket.send("LTRIM $1 $2 $3\c\L" % [key, $start, $stop])
+  r.sendCommand("LTRIM", key, $start, $stop)
   raiseNoOK(r.parseStatus())
 
 proc rPop*(r: TRedis, key: string): TRedisString =
   ## Remove and get the last element in a list
-  r.socket.send("RPOP $1\c\L" % key)
+  r.sendCommand("RPOP", key)
   return r.parseBulk()
   
 proc rPopLPush*(r: TRedis, source, destination: string): TRedisString =
   ## Remove the last element in a list, append it to another list and return it
-  r.socket.send("RPOPLPUSH $1 $2\c\L" % [source, destination])
+  r.sendCommand("RPOPLPUSH", source, destination)
   return r.parseBulk()
   
 proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger =
   ## Append a value to a list. Returns the length of the list after the push.
   ## The ``create`` param specifies whether a list should be created if it
-  ## doesn't exist at ``key``. More specifically if ``create`` is True, `RPUSH` will
-  ## be used, otherwise `RPUSHX`.
+  ## doesn't exist at ``key``. More specifically if ``create`` is True, `RPUSH` 
+  ## will be used, otherwise `RPUSHX`.
   if create:
-    r.socket.send("RPUSH $1 \"$2\"\c\L" % [key, value])
+    r.sendCommand("RPUSH", key, value)
   else:
-    r.socket.send("RPUSHX $1 \"$2\"\c\L" % [key, value])
+    r.sendCommand("RPUSHX", key, value)
   return r.parseInteger()
 
 # Sets
 
 proc sadd*(r: TRedis, key: string, member: string): TRedisInteger =
   ## Add a member to a set
-  r.socket.send("SADD $# \"$#\"\c\L" % [key, member])
+  r.sendCommand("SADD", key, member)
   return r.parseInteger()
 
 proc scard*(r: TRedis, key: string): TRedisInteger =
   ## Get the number of members in a set
-  r.socket.send("SCARD $#\c\L" % key)
+  r.sendCommand("SCARD", key)
   return r.parseInteger()
 
-proc sdiff*(r: TRedis, key: openarray[string]): TRedisList =
+proc sdiff*(r: TRedis, keys: openarray[string]): TRedisList =
   ## Subtract multiple sets
-  r.socket.send("SDIFF $#\c\L" % key)
+  r.sendCommand("SDIFF", keys)
   return r.parseMultiBulk()
 
 proc sdiffstore*(r: TRedis, destination: string,
-                key: openarray[string]): TRedisInteger =
+                keys: openarray[string]): TRedisInteger =
   ## Subtract multiple sets and store the resulting set in a key
-  r.socket.send("SDIFFSTORE $# $#\c\L" % [destination, key.join()])
+  r.sendCommand("SDIFFSTORE", destination, keys)
   return r.parseInteger()
 
-proc sinter*(r: TRedis, key: openarray[string]): TRedisList =
+proc sinter*(r: TRedis, keys: openarray[string]): TRedisList =
   ## Intersect multiple sets
-  r.socket.send("SINTER $#\c\L" % key)
+  r.sendCommand("SINTER", keys)
   return r.parseMultiBulk()
 
 proc sinterstore*(r: TRedis, destination: string,
-                 key: openarray[string]): TRedisInteger =
+                 keys: openarray[string]): TRedisInteger =
   ## Intersect multiple sets and store the resulting set in a key
-  r.socket.send("SINTERSTORE $# $#\c\L" % [destination, key.join()])
+  r.sendCommand("SINTERSTORE", destination, keys)
   return r.parseInteger()
 
 proc sismember*(r: TRedis, key: string, member: string): TRedisInteger =
   ## Determine if a given value is a member of a set
-  r.socket.send("SISMEMBER $# \"$#\"\c\L" % [key, member])
+  r.sendCommand("SISMEMBER", key, member)
   return r.parseInteger()
 
 proc smembers*(r: TRedis, key: string): TRedisList =
   ## Get all the members in a set
-  r.socket.send("SMEMBERS $#\c\L" % key)
+  r.sendCommand("SMEMBERS", key)
   return r.parseMultiBulk()
 
 proc smove*(r: TRedis, source: string, destination: string,
            member: string): TRedisInteger =
   ## Move a member from one set to another
-  r.socket.send("SMOVE $# $# \"$#\"\c\L" % [source, destination, member])
+  r.sendCommand("SMOVE", source, destination, member)
   return r.parseInteger()
 
 proc spop*(r: TRedis, key: string): TRedisString =
   ## Remove and return a random member from a set
-  r.socket.send("SPOP $#\c\L" % key)
+  r.sendCommand("SPOP", key)
   return r.parseBulk()
 
 proc srandmember*(r: TRedis, key: string): TRedisString =
   ## Get a random member from a set
-  r.socket.send("SRANDMEMBER $#\c\L" % key)
+  r.sendCommand("SRANDMEMBER", key)
   return r.parseBulk()
 
 proc srem*(r: TRedis, key: string, member: string): TRedisInteger =
   ## Remove a member from a set
-  r.socket.send("SREM $# \"$#\"\c\L" % [key, member])
+  r.sendCommand("SREM", key, member)
   return r.parseInteger()
 
-proc sunion*(r: TRedis, key: openarray[string]): TRedisList =
+proc sunion*(r: TRedis, keys: openarray[string]): TRedisList =
   ## Add multiple sets
-  r.socket.send("SUNION $#\c\L" % key)
+  r.sendCommand("SUNION", keys)
   return r.parseMultiBulk()
 
 proc sunionstore*(r: TRedis, destination: string,
                  key: openarray[string]): TRedisInteger =
   ## Add multiple sets and store the resulting set in a key 
-  r.socket.send("SUNIONSTORE $# $#\c\L" % [destination, key.join()])
+  r.sendCommand("SUNIONSTORE", destination, key)
   return r.parseInteger()
 
 # Sorted sets
 
 proc zadd*(r: TRedis, key: string, score: int, member: string): TRedisInteger =
   ## Add a member to a sorted set, or update its score if it already exists
-  r.socket.send("ZADD $# $# \"$#\"\c\L" % [key, $score, member])
+  r.sendCommand("ZADD", key, $score, member)
   return r.parseInteger()
 
 proc zcard*(r: TRedis, key: string): TRedisInteger =
   ## Get the number of members in a sorted set
-  r.socket.send("ZCARD $#\c\L" % key)
+  r.sendCommand("ZCARD", key)
   return r.parseInteger()
 
 proc zcount*(r: TRedis, key: string, min: string, max: string): TRedisInteger =
   ## Count the members in a sorted set with scores within the given values
-  r.socket.send("ZCOUNT $# $# $#\c\L" % [key, min, max])
+  r.sendCommand("ZCOUNT", key, min, max)
   return r.parseInteger()
 
 proc zincrby*(r: TRedis, key: string, increment: string,
              member: string): TRedisString =
   ## Increment the score of a member in a sorted set
-  r.socket.send("ZINCRBY $# $# \"$#\"\c\L" % [key, increment, member])
+  r.sendCommand("ZINCRBY", key, increment, member)
   return r.parseBulk()
 
 proc zinterstore*(r: TRedis, destination: string, numkeys: string,
-                 key: openarray[string], weights: openarray[string] = [],
+                 keys: openarray[string], weights: openarray[string] = [],
                  aggregate: string = ""): TRedisInteger =
   ## Intersect multiple sorted sets and store the resulting sorted set in a new key
-  var command = "ZINTERSTORE $# $# $#" % [destination, numkeys, key.join()]
+  var args = @[destination, numkeys]
+  for i in items(keys): args.add(i)
   
   if weights.len != 0:
-    command.add(" " & weights.join())
+    args.add("WITHSCORE")
+    for i in items(weights): args.add(i)
   if aggregate.len != 0:
-    command.add(" " & aggregate.join())
+    args.add("AGGREGATE")
+    args.add(aggregate)
     
-  r.socket.send(command & "\c\L")
+  r.sendCommand("ZINTERSTORE", args)
   
   return r.parseInteger()
 
@@ -556,43 +578,46 @@ proc zrange*(r: TRedis, key: string, start: string, stop: string,
             withScores: bool): TRedisList =
   ## Return a range of members in a sorted set, by index
   if not withScores:
-    r.socket.send("ZRANGE $# $# $#\c\L" % [key, start, stop.join()])
+    r.sendCommand("ZRANGE", key, start, stop)
   else:
-    r.socket.send("ZRANGE $# $# $# WITHSCORES\c\L" % [key, start, stop.join()])
+    r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop)
   return r.parseMultiBulk()
 
 proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, 
                    withScore: bool = false, limit: bool = False,
                    limitOffset: int = 0, limitCount: int = 0): TRedisList =
   ## Return a range of members in a sorted set, by score
-  var command = "ZRANGEBYSCORE $# $# $#" % [key, min, max.join()]
-  
-  if withScore: command.add(" WITHSCORE")
-  if limit: command.add(" LIMIT " & $limitOffset & " " & $limitCount)
+  var args = @[key, min, max]
   
-  r.socket.send(command & "\c\L")
+  if withScore: args.add("WITHSCORE")
+  if limit: 
+    args.add("LIMIT")
+    args.add($limitOffset)
+    args.add($limitCount)
+    
+  r.sendCommand("ZRANGEBYSCORE", args)
   return r.parseMultiBulk()
 
 proc zrank*(r: TRedis, key: string, member: string): TRedisString =
   ## Determine the index of a member in a sorted set
-  r.socket.send("ZRANK $# \"$#\"\c\L" % [key, member])
+  r.sendCommand("ZRANK", key, member)
   return r.parseBulk()
 
 proc zrem*(r: TRedis, key: string, member: string): TRedisInteger =
   ## Remove a member from a sorted set
-  r.socket.send("ZREM $# \"$#\"\c\L" % [key, member])
+  r.sendCommand("ZREM", key, member)
   return r.parseInteger()
 
 proc zremrangebyrank*(r: TRedis, key: string, start: string,
                      stop: string): TRedisInteger =
   ## Remove all members in a sorted set within the given indexes
-  r.socket.send("ZREMRANGEBYRANK $# $# $#\c\L" % [key, start, stop])
+  r.sendCommand("ZREMRANGEBYRANK", key, start, stop)
   return r.parseInteger()
 
 proc zremrangebyscore*(r: TRedis, key: string, min: string,
                       max: string): TRedisInteger =
   ## Remove all members in a sorted set within the given scores
-  r.socket.send("ZREMRANGEBYSCORE $# $# $#\c\L" % [key, min, max])
+  r.sendCommand("ZREMRANGEBYSCORE", key, min, max)
   return r.parseInteger()
 
 proc zrevrange*(r: TRedis, key: string, start: string, stop: string,
@@ -600,9 +625,8 @@ proc zrevrange*(r: TRedis, key: string, start: string, stop: string,
   ## Return a range of members in a sorted set, by index, 
   ## with scores ordered from high to low
   if withScore:
-    r.socket.send("ZREVRANGE $# $# $# WITHSCORE\c\L" %
-                  [key, start, stop.join()])
-  else: r.socket.send("ZREVRANGE $# $# $#\c\L" % [key, start, stop.join()])
+    r.sendCommand("ZREVRANGE", "WITHSCORE", key, start, stop)
+  else: r.sendCommand("ZREVRANGE", key, start, stop)
   return r.parseMultiBulk()
 
 proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, 
@@ -610,37 +634,43 @@ proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string,
                    limitOffset: int = 0, limitCount: int = 0): TRedisList =
   ## Return a range of members in a sorted set, by score, with
   ## scores ordered from high to low
-  var command = "ZREVRANGEBYSCORE $# $# $#" % [key, min, max.join()]
+  var args = @[key, min, max]
   
-  if withScore: command.add(" WITHSCORE")
-  if limit: command.add(" LIMIT " & $limitOffset & " " & $limitCount)
+  if withScore: args.add("WITHSCORE")
+  if limit: 
+    args.add("LIMIT")
+    args.add($limitOffset)
+    args.add($limitCount)
   
-  r.socket.send(command & "\c\L")
+  r.sendCommand("ZREVRANGEBYSCORE", args)
   return r.parseMultiBulk()
 
 proc zrevrank*(r: TRedis, key: string, member: string): TRedisString =
   ## Determine the index of a member in a sorted set, with
   ## scores ordered from high to low
-  r.socket.send("ZREVRANK $# \"$#\"\c\L" % [key, member])
+  r.sendCommand("ZREVRANK", key, member)
   return r.parseBulk()
 
 proc zscore*(r: TRedis, key: string, member: string): TRedisString =
   ## Get the score associated with the given member in a sorted set
-  r.socket.send("ZSCORE $# \"$#\"\c\L" % [key, member])
+  r.sendCommand("ZSCORE", key, member)
   return r.parseBulk()
 
 proc zunionstore*(r: TRedis, destination: string, numkeys: string,
-                 key: openarray[string], weights: openarray[string] = [],
+                 keys: openarray[string], weights: openarray[string] = [],
                  aggregate: string = ""): TRedisInteger =
   ## Add multiple sorted sets and store the resulting sorted set in a new key 
-  var command = "ZUNIONSTORE $# $# $#" % [destination, numkeys, key.join()]
+  var args = @[destination, numkeys]
+  for i in items(keys): args.add(i)
   
   if weights.len != 0:
-    command.add(" " & weights.join())
+    args.add("WEIGHTS")
+    for i in items(weights): args.add(i)
   if aggregate.len != 0:
-    command.add(" " & aggregate.join())
+    args.add("AGGREGATE")
+    args.add(aggregate)
     
-  r.socket.send(command & "\c\L")
+  r.sendCommand("ZUNIONSTORE", args)
   
   return r.parseInteger()
 
@@ -678,117 +708,117 @@ proc unsubscribe*(r: TRedis, [channel: openarray[string], : string): ???? =
 
 # Transactions
 
-proc discardCmds*(r: TRedis) =
+proc discardMulti*(r: TRedis) =
   ## Discard all commands issued after MULTI
-  r.socket.send("DISCARD\c\L")
+  r.sendCommand("DISCARD")
   raiseNoOK(r.parseStatus())
 
 proc exec*(r: TRedis): TRedisList =
   ## Execute all commands issued after MULTI
-  r.socket.send("EXEC\c\L")
+  r.sendCommand("EXEC")
   return r.parseMultiBulk()
 
 proc multi*(r: TRedis) =
   ## Mark the start of a transaction block
-  r.socket.send("MULTI\c\L")
+  r.sendCommand("MULTI")
   raiseNoOK(r.parseStatus())
 
 proc unwatch*(r: TRedis) =
   ## Forget about all watched keys
-  r.socket.send("UNWATCH\c\L")
+  r.sendCommand("UNWATCH")
   raiseNoOK(r.parseStatus())
 
 proc watch*(r: TRedis, key: openarray[string]) =
   ## Watch the given keys to determine execution of the MULTI/EXEC block 
-  r.socket.send("WATCH $#\c\L" % key.join())
+  r.sendCommand("WATCH", key)
   raiseNoOK(r.parseStatus())
 
 # Connection
 
 proc auth*(r: TRedis, password: string) =
   ## Authenticate to the server
-  r.socket.send("AUTH $#\c\L" % password)
+  r.sendCommand("AUTH", password)
   raiseNoOK(r.parseStatus())
 
 proc echoServ*(r: TRedis, message: string): TRedisString =
   ## Echo the given string
-  r.socket.send("ECHO $#\c\L" % message)
+  r.sendCommand("ECHO", message)
   return r.parseBulk()
 
 proc ping*(r: TRedis): TRedisStatus =
   ## Ping the server
-  r.socket.send("PING\c\L")
+  r.sendCommand("PING")
   return r.parseStatus()
 
 proc quit*(r: TRedis) =
   ## Close the connection
-  r.socket.send("QUIT\c\L")
+  r.sendCommand("QUIT")
   raiseNoOK(r.parseStatus())
 
-proc select*(r: TRedis, index: string): TRedisStatus =
+proc select*(r: TRedis, index: int): TRedisStatus =
   ## Change the selected database for the current connection 
-  r.socket.send("SELECT $#\c\L" % index)
+  r.sendCommand("SELECT", $index)
   return r.parseStatus()
 
 # Server
 
 proc bgrewriteaof*(r: TRedis) =
   ## Asynchronously rewrite the append-only file
-  r.socket.send("BGREWRITEAOF\c\L")
+  r.sendCommand("BGREWRITEAOF")
   raiseNoOK(r.parseStatus())
 
 proc bgsave*(r: TRedis) =
   ## Asynchronously save the dataset to disk
-  r.socket.send("BGSAVE\c\L")
+  r.sendCommand("BGSAVE")
   raiseNoOK(r.parseStatus())
 
-proc configGet*(r: TRedis, parameter: string): TRedisString =
+proc configGet*(r: TRedis, parameter: string): TRedisList =
   ## Get the value of a configuration parameter
-  r.socket.send("CONFIG GET $#\c\L" % parameter)
-  return r.parseBulk()
+  r.sendCommand("CONFIG", "GET", parameter)
+  return r.parseMultiBulk()
 
 proc configSet*(r: TRedis, parameter: string, value: string) =
   ## Set a configuration parameter to the given value
-  r.socket.send("CONFIG SET $# $#\c\L" % [parameter, value])
+  r.sendCommand("CONFIG", "SET", parameter, value)
   raiseNoOK(r.parseStatus())
 
 proc configResetStat*(r: TRedis) =
   ## Reset the stats returned by INFO
-  r.socket.send("CONFIG RESETSTAT\c\L")
+  r.sendCommand("CONFIG", "RESETSTAT")
   raiseNoOK(r.parseStatus())
 
 proc dbsize*(r: TRedis): TRedisInteger =
   ## Return the number of keys in the selected database
-  r.socket.send("DBSIZE\c\L")
+  r.sendCommand("DBSIZE")
   return r.parseInteger()
 
 proc debugObject*(r: TRedis, key: string): TRedisStatus =
   ## Get debugging information about a key
-  r.socket.send("DEBUG OBJECT $#\c\L" % key)
+  r.sendCommand("DEBUG", "OBJECT", key)
   return r.parseStatus()
 
 proc debugSegfault*(r: TRedis) =
   ## Make the server crash
-  r.socket.send("DEBUG-SEGFAULT\c\L")
+  r.sendCommand("DEBUG", "SEGFAULT")
 
 proc flushall*(r: TRedis): TRedisStatus =
   ## Remove all keys from all databases
-  r.socket.send("FLUSHALL\c\L")
+  r.sendCommand("FLUSHALL")
   raiseNoOK(r.parseStatus())
 
 proc flushdb*(r: TRedis): TRedisStatus =
   ## Remove all keys from the current database
-  r.socket.send("FLUSHDB\c\L")
+  r.sendCommand("FLUSHDB")
   raiseNoOK(r.parseStatus())
 
 proc info*(r: TRedis): TRedisString =
   ## Get information and statistics about the server
-  r.socket.send("INFO\c\L")
+  r.sendCommand("INFO")
   return r.parseBulk()
 
 proc lastsave*(r: TRedis): TRedisInteger =
   ## Get the UNIX time stamp of the last successful save to disk
-  r.socket.send("LASTSAVE\c\L")
+  r.sendCommand("LASTSAVE")
   return r.parseInteger()
 
 discard """
@@ -800,32 +830,44 @@ proc monitor*(r: TRedis) =
 
 proc save*(r: TRedis) =
   ## Synchronously save the dataset to disk
-  r.socket.send("SAVE\c\L")
+  r.sendCommand("SAVE")
   raiseNoOK(r.parseStatus())
 
-proc shutdown*(r: TRedis): TRedisStatus =
+proc shutdown*(r: TRedis) =
   ## Synchronously save the dataset to disk and then shut down the server
-  r.socket.send("SHUTDOWN\c\L")
+  r.sendCommand("SHUTDOWN")
   var s = r.socket.recv()
   if s != "": raise newException(ERedis, s)
 
 proc slaveof*(r: TRedis, host: string, port: string) =
   ## Make the server a slave of another instance, or promote it as master
-  r.socket.send("SLAVEOF $# $#\c\L" % [host, port])
+  r.sendCommand("SLAVEOF", host, port)
   raiseNoOK(r.parseStatus())
 
 when isMainModule:
   var r = open()
-  r.setk("nim:test", "WORKS!!!")
+  r.auth("pass")
+
+  r.setk("nim:test", "Testing something.")
   r.setk("nim:utf8", "こんにちは")
+  r.setk("nim:esc", "\\ths ągt\\")
+  
+  echo r.get("nim:esc")
   echo r.incr("nim:int")
   echo r.incr("nim:int")
   echo r.get("nim:int")
   echo r.get("nim:utf8")
   echo repr(r.get("blahasha"))
+  echo r.randomKey()
+  
   var p = r.lrange("mylist", 0, -1)
   for i in items(p):
     echo("  ", i)
 
   echo(r.debugObject("test"))
 
+  r.configSet("timeout", "299")
+  for i in items(r.configGet("timeout")): echo ">> ", i
+
+  echo r.echoServ("BLAH")
+