Permalink
Browse files

added initial implementation code & examples

  • Loading branch information...
1 parent b5b6ab0 commit d531a5976de60a27bf5191e9b306c759065ea7a2 @bakkdoor committed Oct 26, 2011
Showing with 256 additions and 0 deletions.
  1. +2 −0 README
  2. +14 −0 examples/get_set.fy
  3. +15 −0 examples/incr_decr.fy
  4. +16 −0 examples/lists.fy
  5. +14 −0 examples/thread_safety.fy
  6. +3 −0 lib/redis.fy
  7. +108 −0 lib/redis/client.fy
  8. +78 −0 lib/redis/connection.fy
  9. +6 −0 lib/redis/error.fy
View
2 README
@@ -0,0 +1,2 @@
+redis.fy is a Redis client library for Fancy.
+It's still a workin progress, but most commands should work as expected.
View
@@ -0,0 +1,14 @@
+require: "redis"
+
+r = Redis Client new
+
+r["msg"]: "hello world" # SET
+r["msg"] println # GET
+
+# alternatively:
+r('set, "msg2", "test!")
+r('get, "msg2") println
+
+# or even:
+r call: ('set, "msg3", "test3!")
+r call: ('get, "msg3") println
View
@@ -0,0 +1,15 @@
+require: "redis"
+
+r = Redis Client new
+
+"incr:" println
+r('del, 'counter)
+
+10 times: {
+ r('incr, 'counter) println
+}
+
+"\ndecr:" println
+10 times: {
+ r('decr, 'counter) println
+}
View
@@ -0,0 +1,16 @@
+require: "redis"
+
+r = Redis Client new
+
+"Adding messages to a list:" println
+r('rpush, 'list, "hello, world")
+r('rpush, 'list, "this is a message")
+r('rpush, 'list, "this is another one")
+
+"Contents are: " println
+r('lrange, 'list, 0, -1) inspect println
+
+
+"Only keeping first 2 elements:" println
+r('ltrim, 'list, 0, 1)
+r('lrange, 'list, 0, -1) inspect println
View
@@ -0,0 +1,14 @@
+require: "redis"
+
+r = Redis Client new
+
+threads = []
+10 times: |i| {
+ t = Thread new: {
+ r('set, "foo", "test: #{i}")
+ r('get, "foo") println
+ }
+ threads << t
+}
+
+threads each: 'join
View
@@ -0,0 +1,3 @@
+require: "redis/error"
+require: "redis/connection"
+require: "redis/client"
View
@@ -0,0 +1,108 @@
+class Redis {
+ class Client {
+ DefaultHost = "localhost"
+ DefaultPort = 6379
+ DefaultUser = "anonymous"
+ DefaultPassword = "anonymous"
+
+ def initialize: host port: port user: user password: password {
+ @connection = Connection new: host port: port user: user password: password
+ @connection open
+ @thread_safe = true
+ @mutex = Mutex new
+ }
+
+ def disable_thread_safety! {
+ @thread_safe = false
+ def @mutex synchronize: block {
+ block call
+ }
+ }
+
+ def thread_safe? {
+ @thread_safe
+ }
+
+ def initialize: host (DefaultHost) user: user (DefaultUser) password: password (DefaultPassword) {
+ initialize: host port: DefaultPort user: user password: password
+ }
+
+ def call: command {
+ cmd_name = command first
+
+ match cmd_name {
+ case 'hgetall -> return hgetall: command
+ case 'keys -> return keys: command
+ }
+
+ reply = command: command
+
+ match cmd_name {
+ case 'smove -> boolean: reply
+
+ case 'sadd ->
+ match command skip: 2 . size {
+ case 1 -> boolean: reply
+ case _ -> reply
+ }
+
+ case 'srem ->
+ match command skip: 2 . size {
+ case 1 -> boolean: reply
+ case _ -> repl
+ }
+
+ case _ -> reply
+ }
+ }
+
+ # special commands handled differently
+
+ def [key]: value {
+ call: ('set, key, value)
+ }
+
+ def [key] {
+ call: ('get, key)
+ }
+
+ def hgetall: command {
+ reply = command: command
+ match reply {
+ case Array ->
+ h = <[]>
+ reply in_groups_of: 2 . each: |pair| {
+ field, value = pair
+ h[field]: value
+ }
+ h
+ case _ -> reply
+ }
+ }
+
+ def keys: command {
+ reply = command: command
+ match reply {
+ case String -> reply split: " "
+ case _ -> reply
+ }
+ }
+
+ # private
+
+ def command: command {
+ @mutex synchronize: {
+ @connection send_command: command
+ @connection read_reply
+ }
+ }
+
+ def boolean: reply {
+ reply to_i == 1
+ }
+
+ def disconnect {
+ @connection close
+ }
+ }
+}
View
@@ -0,0 +1,78 @@
+class Redis {
+ class Connection {
+ class ProtocolError : StandardError
+
+ DELIMITER = "\r\n"
+ MINUS = "-"
+ PLUS = "+"
+ COLON = ":"
+ DOLLAR = "$"
+ ASTERISK = "*"
+
+ read_slots: ('host, 'port, 'user, 'password)
+ def initialize: @host port: @port user: @user password: @password
+
+ def open {
+ @sock = TCPSocket open: @host port: @port
+ @sock setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+ }
+
+ def connected? {
+ @sock not not
+ }
+
+ def close {
+ @sock close
+ }
+
+ def send_command: params {
+ @sock send: $ build_command: params
+ }
+
+ def build_command: params {
+ command = ""
+ command << "*#{params size}"
+ command << DELIMITER
+ params each: |p| {
+ p = p to_s
+ command << "$#{p size}"
+ command << DELIMITER
+ command << p
+ command << DELIMITER
+ }
+ command
+ }
+
+ def read_reply {
+ reply = @sock read: 1
+ { raise(Errno ECONNRESET) } unless: reply
+ data = @sock readline
+ format_reply: reply data: data
+ }
+
+ def format_reply: reply data: data {
+ match reply {
+ case MINUS -> Error new: $ data strip
+ case PLUS -> data strip
+ case COLON -> data to_i
+ case DOLLAR -> bulk_reply: data
+ case ASTERISK -> multi_bulk_reply: data
+ case _ -> ProtocolError new: reply . raise!
+ }
+ }
+
+ def bulk_reply: data {
+ length = data to_i
+ { return nil } if: (length == -1)
+ reply = @sock read: length
+ @sock read: 2 # DELIMITER
+ reply
+ }
+
+ def multi_bulk_reply: data {
+ length = data to_i
+ { return nil } if: (length == -1)
+ Array new: length with: { read_reply }
+ }
+ }
+}
View
@@ -0,0 +1,6 @@
+class Redis {
+ class Error {
+ read_slot: 'message
+ def initialize: @message
+ }
+}

0 comments on commit d531a59

Please sign in to comment.