remq 0.0.1a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
 - data/.gitmodules +3 -0
 - data/LICENSE +22 -0
 - data/Readme.md +49 -0
 - data/lib/remq.rb +52 -0
 - data/lib/remq/coder.rb +30 -0
 - data/lib/remq/multi_json_coder.rb +39 -0
 - data/lib/remq/script.rb +27 -0
 - data/lib/remq/version.rb +3 -0
 - data/remq.gemspec +34 -0
 - data/spec/remq_spec.rb +67 -0
 - data/vendor/remq/Readme.md +41 -0
 - data/vendor/remq/scripts/consume.lua +45 -0
 - data/vendor/remq/scripts/publish.lua +12 -0
 - data/vendor/remq/scripts/purge.lua +18 -0
 - metadata +110 -0
 
    
        data/.gitignore
    ADDED
    
    
    
        data/.gitmodules
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2012 Evan Owen
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            MIT License
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 6 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 7 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 8 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 9 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 10 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 11 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 14 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 17 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 18 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 19 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 20 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 21 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 22 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
    
        data/Readme.md
    ADDED
    
    | 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Remq-rb
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            A Ruby client library for [Remq](https://github.com/kainosnoema/remq), a [Redis](http://redis.io)-based protocol for building fast, persistent pub/sub message queues.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            NOTE: In early-stage development, API not locked.
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            **Producer:**
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            ``` rb
         
     | 
| 
      
 12 
     | 
    
         
            +
            require 'remq'
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            remq = Remq.new
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            message = { event: 'signup', account_id: 694 }
         
     | 
| 
      
 17 
     | 
    
         
            +
            id = remq.publish('events.accounts', message)
         
     | 
| 
      
 18 
     | 
    
         
            +
            ```
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            **Pub/sub consumer (messages lost during failure):**
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            ``` rb
         
     | 
| 
      
 23 
     | 
    
         
            +
            require 'remq'
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            remq = Remq.new
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            # TODO: not implemented yet
         
     | 
| 
      
 28 
     | 
    
         
            +
            ```
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            **Polling consumer with cursor (resumes post-failure):**
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            ``` rb
         
     | 
| 
      
 33 
     | 
    
         
            +
            require 'remq'
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            remq = Remq.new
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            # TODO: not implemented yet
         
     | 
| 
      
 38 
     | 
    
         
            +
            ```
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            **Purging old messages:**
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            ``` rb
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
            require 'remq'
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            remq = Remq.new
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            # TODO: not implemented yet
         
     | 
| 
      
 49 
     | 
    
         
            +
            ```
         
     | 
    
        data/lib/remq.rb
    ADDED
    
    | 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'redis'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'remq/script'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'remq/multi_json_coder'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            class Remq
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              attr :redis, :namespace, :scripts, :coder
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              def initialize(options = {})
         
     | 
| 
      
 10 
     | 
    
         
            +
                @redis = options[:redis] || Redis.new(options)
         
     | 
| 
      
 11 
     | 
    
         
            +
                @namespace = 'remq'
         
     | 
| 
      
 12 
     | 
    
         
            +
                @scripts = {}
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def publish(channel, message)
         
     | 
| 
      
 16 
     | 
    
         
            +
                channel = channel.join('.') if channel.is_a?(Array)
         
     | 
| 
      
 17 
     | 
    
         
            +
                msg, utc_seconds = coder.encode(message), Time.now.to_i
         
     | 
| 
      
 18 
     | 
    
         
            +
                id = eval_script(:publish, namespace, channel, msg, utc_seconds)
         
     | 
| 
      
 19 
     | 
    
         
            +
                id.nil? ? nil : id.to_s
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              def consume(channel, options = {})
         
     | 
| 
      
 23 
     | 
    
         
            +
                cursor, limit = options[:cursor] || 0, options[:limit] || 1000
         
     | 
| 
      
 24 
     | 
    
         
            +
                msgs_ids = eval_script(:consume, namespace, channel, cursor, limit)
         
     | 
| 
      
 25 
     | 
    
         
            +
                msgs = {}
         
     | 
| 
      
 26 
     | 
    
         
            +
                msgs_ids.each_index { |i|
         
     | 
| 
      
 27 
     | 
    
         
            +
                  if i % 2 == 0
         
     | 
| 
      
 28 
     | 
    
         
            +
                    msgs[msgs_ids[i+1]] = coder.decode(msgs_ids[i])
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
                }
         
     | 
| 
      
 31 
     | 
    
         
            +
                msgs
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              def coder
         
     | 
| 
      
 35 
     | 
    
         
            +
                @coder ||= MultiJsonCoder.new
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              protected
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def eval_script(name, *args)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  script(name).eval(@redis, *args)
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def script(name)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @scripts[name.to_sym] ||= Script.new(File.read(script_path(name)))
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def script_path(name)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  File.expand_path("../vendor/remq/scripts/#{name}.lua", File.dirname(__FILE__))
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/remq/coder.rb
    ADDED
    
    | 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
             
     | 
| 
      
 2 
     | 
    
         
            +
            # borrowed from Resque::Coder. Thanks @defunkt!
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            class Remq
         
     | 
| 
      
 5 
     | 
    
         
            +
              class EncodeException < StandardError; end
         
     | 
| 
      
 6 
     | 
    
         
            +
              class DecodeException < StandardError; end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              class Coder
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Given a Ruby object, returns a string suitable for storage in a
         
     | 
| 
      
 10 
     | 
    
         
            +
                # queue.
         
     | 
| 
      
 11 
     | 
    
         
            +
                def encode(object)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  raise EncodeException
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # alias for encode
         
     | 
| 
      
 16 
     | 
    
         
            +
                def dump(object)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  encode(object)
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                # Given a string, returns a Ruby object.
         
     | 
| 
      
 21 
     | 
    
         
            +
                def decode(object)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  raise DecodeException
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                # alias for decode
         
     | 
| 
      
 26 
     | 
    
         
            +
                def load(object)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  decode(object)
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'multi_json'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'remq/coder'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            # borrowed from Resque::MultiJsonCoder. Thanks @defunkt!
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            if MultiJson.respond_to?(:adapter)
         
     | 
| 
      
 7 
     | 
    
         
            +
              raise "Please install the yajl-ruby or json gem" if MultiJson.adapter.to_s == 'MultiJson::Adapters::OkJson'
         
     | 
| 
      
 8 
     | 
    
         
            +
            elsif MultiJson.respond_to?(:engine)
         
     | 
| 
      
 9 
     | 
    
         
            +
              raise "Please install the yajl-ruby or json gem" if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
         
     | 
| 
      
 10 
     | 
    
         
            +
            end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            class Remq
         
     | 
| 
      
 13 
     | 
    
         
            +
              class MultiJsonCoder < Coder
         
     | 
| 
      
 14 
     | 
    
         
            +
                class EncodeException < StandardError; end
         
     | 
| 
      
 15 
     | 
    
         
            +
                class DecodeException < StandardError; end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def encode(object)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    MultiJson.dump object
         
     | 
| 
      
 20 
     | 
    
         
            +
                  else
         
     | 
| 
      
 21 
     | 
    
         
            +
                    MultiJson.encode object
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def decode(object)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  return unless object
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 29 
     | 
    
         
            +
                    if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      MultiJson.load object
         
     | 
| 
      
 31 
     | 
    
         
            +
                    else
         
     | 
| 
      
 32 
     | 
    
         
            +
                      MultiJson.decode object
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  rescue ::MultiJson::DecodeError => e
         
     | 
| 
      
 35 
     | 
    
         
            +
                    raise DecodeException, e.message, e.backtrace
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/remq/script.rb
    ADDED
    
    | 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'digest/sha1'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class Remq
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Script
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                attr :source, :sha
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(source)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @source = source
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def eval(redis, *args)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  redis.evalsha(sha, [], [*args])
         
     | 
| 
      
 14 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 15 
     | 
    
         
            +
                  if e.message =~ /NOSCRIPT/
         
     | 
| 
      
 16 
     | 
    
         
            +
                    redis.eval(source, [], [*args])
         
     | 
| 
      
 17 
     | 
    
         
            +
                  else
         
     | 
| 
      
 18 
     | 
    
         
            +
                    raise
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def sha
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @sha ||= Digest::SHA1.hexdigest source
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/remq/version.rb
    ADDED
    
    
    
        data/remq.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            $LOAD_PATH.unshift 'lib'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'remq/version'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 6 
     | 
    
         
            +
              s.name              = "remq"
         
     | 
| 
      
 7 
     | 
    
         
            +
              s.version           = Remq::VERSION
         
     | 
| 
      
 8 
     | 
    
         
            +
              s.date              = Time.now.strftime('%Y-%m-%d')
         
     | 
| 
      
 9 
     | 
    
         
            +
              s.summary           = "A Remq client library for Ruby."
         
     | 
| 
      
 10 
     | 
    
         
            +
              s.homepage          = "http://github.com/kainosnoema/remq"
         
     | 
| 
      
 11 
     | 
    
         
            +
              s.email             = "kainosnoema@gmail.com"
         
     | 
| 
      
 12 
     | 
    
         
            +
              s.authors           = [ "Evan Owen" ]
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              s.files             = `git ls-files`.split("\n")
         
     | 
| 
      
 15 
     | 
    
         
            +
              s.files            += Dir.glob('vendor/**/*')
         
     | 
| 
      
 16 
     | 
    
         
            +
              s.test_files        = `git ls-files -- {test,spec,features}/*`.split("\n")
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              s.add_dependency    "redis",      "~> 3.0.1"
         
     | 
| 
      
 19 
     | 
    
         
            +
              s.add_dependency    "multi_json", "~> 1.0"
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              s.add_development_dependency "rake"
         
     | 
| 
      
 22 
     | 
    
         
            +
              s.add_development_dependency "rspec", "~> 2.6"
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
              s.description = <<description
         
     | 
| 
      
 25 
     | 
    
         
            +
                Remq is a Redis-based protocol for building fast, persistent
         
     | 
| 
      
 26 
     | 
    
         
            +
                pub/sub message queues.
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                The Remq protocol is defined by a collection of Lua scripts
         
     | 
| 
      
 29 
     | 
    
         
            +
                (located at https://github.com/kainosnoema/remq) which effectively
         
     | 
| 
      
 30 
     | 
    
         
            +
                turn Redis into a capable message queue broker for fast inter-service
         
     | 
| 
      
 31 
     | 
    
         
            +
                communication. The Remq Ruby client library is built on top of these
         
     | 
| 
      
 32 
     | 
    
         
            +
                scripts, making it easy to build fast, persisted pub/sub message queues.
         
     | 
| 
      
 33 
     | 
    
         
            +
            description
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/remq_spec.rb
    ADDED
    
    | 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'remq'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe Remq do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              before :all do
         
     | 
| 
      
 6 
     | 
    
         
            +
                subject { Remq.new(db: 4).tap { |r| r.redis.flushdb } }
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              after :each do
         
     | 
| 
      
 10 
     | 
    
         
            +
                subject.redis.flushdb
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              context "class" do
         
     | 
| 
      
 14 
     | 
    
         
            +
                describe ".new" do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  it "creates a Redis client when options hash missing" do
         
     | 
| 
      
 16 
     | 
    
         
            +
                    Remq.new.redis.should be_a(Redis)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  it "takes a Redis client as an option" do
         
     | 
| 
      
 20 
     | 
    
         
            +
                    Remq.new(redis: (redis = Redis.new)).redis.should eql redis
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              describe ".publish" do
         
     | 
| 
      
 26 
     | 
    
         
            +
                it "publishes a message to the given channel and returns an id" do
         
     | 
| 
      
 27 
     | 
    
         
            +
                  id = subject.publish('events.things', { test: 'one' })
         
     | 
| 
      
 28 
     | 
    
         
            +
                  id.should be_a String
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              describe ".consume" do
         
     | 
| 
      
 33 
     | 
    
         
            +
                it "consumes messages published to the given channel" do
         
     | 
| 
      
 34 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'one' })
         
     | 
| 
      
 35 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'two' })
         
     | 
| 
      
 36 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'three' })
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  msgs = subject.consume('events.things')
         
     | 
| 
      
 39 
     | 
    
         
            +
                  msgs.should have(3).items
         
     | 
| 
      
 40 
     | 
    
         
            +
                  msgs[msgs.keys[0]].should eql({ 'test' => 'one' })
         
     | 
| 
      
 41 
     | 
    
         
            +
                  msgs[msgs.keys[2]].should eql({ 'test' => 'three' })
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                it "limits the messages returned to value given in the :limit option" do
         
     | 
| 
      
 45 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'one' })
         
     | 
| 
      
 46 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'two' })
         
     | 
| 
      
 47 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'three' })
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  msgs = subject.consume('events.things', limit: 2)
         
     | 
| 
      
 50 
     | 
    
         
            +
                  msgs.should have(2).items
         
     | 
| 
      
 51 
     | 
    
         
            +
                  msgs[msgs.keys[0]].should eql({ 'test' => 'one' })
         
     | 
| 
      
 52 
     | 
    
         
            +
                  msgs[msgs.keys[1]].should eql({ 'test' => 'two' })
         
     | 
| 
      
 53 
     | 
    
         
            +
                  msgs[msgs.keys[2]].should be_nil
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                it "returns messages published since the id given in the :cursor option" do
         
     | 
| 
      
 57 
     | 
    
         
            +
                  cursor = subject.publish('events.things', { 'test' => 'one' })
         
     | 
| 
      
 58 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'two' })
         
     | 
| 
      
 59 
     | 
    
         
            +
                  subject.publish('events.things', { 'test' => 'three' })
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  msgs = subject.consume('events.things', cursor: cursor)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  msgs.should have(2).items
         
     | 
| 
      
 63 
     | 
    
         
            +
                  msgs[msgs.keys[0]].should eql({ 'test' => 'two' })
         
     | 
| 
      
 64 
     | 
    
         
            +
                  msgs[msgs.keys[1]].should eql({ 'test' => 'three' })
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,41 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Remq
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Remq (pronounced 'rem-que') is two things: (1) A [Redis](http://redis.io)-based protocol defined by a collection of Lua scripts (this project) which effectively turn Redis into a capable message queue broker for fast inter-service communication. (2) Multiple client libraries using these scripts for building fast, persisted pub/sub message queues.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              - Producers publish any string to a message channel and receive a unique message-id
         
     | 
| 
      
 6 
     | 
    
         
            +
              - Consumers subscribe to message channels via polling with a cursor (allowing resume), or via Redis pub/sub
         
     | 
| 
      
 7 
     | 
    
         
            +
              - Consumers can subscribe to multible queues at once using Redis key globbing (ie. `'events.*'`)
         
     | 
| 
      
 8 
     | 
    
         
            +
              - Able to sustain ~15k messages/sec on loopback interface (1 producer -> 1 consumer)
         
     | 
| 
      
 9 
     | 
    
         
            +
              - Consistent performance if Redis has enough memory (tested up to ~15m messages, 3GB in memory)
         
     | 
| 
      
 10 
     | 
    
         
            +
              - Purge channels of old messages periodically to maintain performance
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            NOTE: In early-stage development, API not locked.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            ## Client Libraries
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            - Node.js: [remq-node](https://github.com/kainosnoema/remq-node) (`npm install remq`)
         
     | 
| 
      
 17 
     | 
    
         
            +
            - Ruby: [remq-rb](https://github.com/kainosnoema/remq-rb) (`gem install remq`)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            ## Usage
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            This project includes just the core Lua scripts that define the Remq protocol. To use Remq to build a message queue, install Redis along with one or more of the client libraries listed above.
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            Raw Redis syntax:
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            **Producer:**
         
     | 
| 
      
 26 
     | 
    
         
            +
            ``` sh
         
     | 
| 
      
 27 
     | 
    
         
            +
            redis> EVAL <publish.lua> 0 namespace channel message utcseconds
         
     | 
| 
      
 28 
     | 
    
         
            +
            # returns a unique message id
         
     | 
| 
      
 29 
     | 
    
         
            +
            ```
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            **Consumer:**
         
     | 
| 
      
 32 
     | 
    
         
            +
            ``` sh
         
     | 
| 
      
 33 
     | 
    
         
            +
            redis> EVAL <consume.lua> 0 namespace channel cursor limit
         
     | 
| 
      
 34 
     | 
    
         
            +
            # returns each message followed by its id, just like ZRANGEBYSCORE
         
     | 
| 
      
 35 
     | 
    
         
            +
            ```
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            **Purge:**
         
     | 
| 
      
 38 
     | 
    
         
            +
            ``` sh
         
     | 
| 
      
 39 
     | 
    
         
            +
            redis> EVAL <purge.lua> 0 namespace channel <BEFORE id (or) KEEP count>
         
     | 
| 
      
 40 
     | 
    
         
            +
            # returns the count of messages purged
         
     | 
| 
      
 41 
     | 
    
         
            +
            ```
         
     | 
| 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            local namespace, channel, cursor, limit = ARGV[1], ARGV[2], ARGV[3], ARGV[4]
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            limit = math.min(limit, 3999) -- 3999 is the limit of unpack()
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            local channel_key = namespace .. ':channel:' .. channel
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            -- for results from multiple channels, we'll merge them into a single set
         
     | 
| 
      
 8 
     | 
    
         
            +
            local union_key
         
     | 
| 
      
 9 
     | 
    
         
            +
            if string.find(channel_key, '*') then
         
     | 
| 
      
 10 
     | 
    
         
            +
              -- if the pattern matches multiple keys, we have to merge the keys
         
     | 
| 
      
 11 
     | 
    
         
            +
              -- we could use zunionstore here, but it wouldn't be optimal for very large sets
         
     | 
| 
      
 12 
     | 
    
         
            +
              local matched_keys = redis.call('keys', channel_key)
         
     | 
| 
      
 13 
     | 
    
         
            +
              if #matched_keys > 1 then
         
     | 
| 
      
 14 
     | 
    
         
            +
                union_key = channel_key .. '@' .. redis.call('get', namespace .. ':id')
         
     | 
| 
      
 15 
     | 
    
         
            +
                for i,key in ipairs(matched_keys) do
         
     | 
| 
      
 16 
     | 
    
         
            +
                  local msgs_ids = redis.call('zrangebyscore', key, '(' .. cursor, '+inf', 'WITHSCORES', 'LIMIT', 0, limit)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  if #msgs_ids > 0 then
         
     | 
| 
      
 18 
     | 
    
         
            +
                    -- `zadd` takes scores first, so we have to reverse
         
     | 
| 
      
 19 
     | 
    
         
            +
                    local len, reversed = #msgs_ids, {}
         
     | 
| 
      
 20 
     | 
    
         
            +
                    for i = len, 1, -1 do reversed[len - i + 1] = msgs_ids[i] end
         
     | 
| 
      
 21 
     | 
    
         
            +
                    redis.call('zadd', union_key, unpack(reversed))
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
                channel_key = union_key
         
     | 
| 
      
 25 
     | 
    
         
            +
              else
         
     | 
| 
      
 26 
     | 
    
         
            +
                channel_key = matched_keys[1]
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            -- as long as we have a channel key, get the messages and add wrap with them with ids
         
     | 
| 
      
 31 
     | 
    
         
            +
            local msgs = {}
         
     | 
| 
      
 32 
     | 
    
         
            +
            if channel_key ~= nil then
         
     | 
| 
      
 33 
     | 
    
         
            +
              msgs = redis.call('zrangebyscore', channel_key, '(' .. cursor, '+inf', 'WITHSCORES', 'LIMIT', 0, limit)
         
     | 
| 
      
 34 
     | 
    
         
            +
              -- zset decimal precision isn't great enough to retain utc seconds, so we have to round
         
     | 
| 
      
 35 
     | 
    
         
            +
              for i,key in ipairs(msgs) do
         
     | 
| 
      
 36 
     | 
    
         
            +
                if i % 2 == 0 then msgs[i] = string.format("%.10f", msgs[i]) end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            -- if we've merged multiple channels, remove the union key
         
     | 
| 
      
 41 
     | 
    
         
            +
            if union_key ~= nil then
         
     | 
| 
      
 42 
     | 
    
         
            +
              redis.call('del', union_key)
         
     | 
| 
      
 43 
     | 
    
         
            +
            end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            return msgs
         
     | 
| 
         @@ -0,0 +1,12 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            local namespace, channel, msg, utc_sec = ARGV[1], ARGV[2], ARGV[3], ARGV[4]
         
     | 
| 
      
 2 
     | 
    
         
            +
            local channel_key = namespace .. ':channel:' .. channel
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            -- ids are an incrementing integer followed by UTC time as a decimal value
         
     | 
| 
      
 5 
     | 
    
         
            +
            local id = redis.call('incr', namespace .. ':id') .. '.' .. (utc_sec or 0)
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            redis.call('zadd', channel_key, id, msg)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            redis.call('publish', channel_key, msg)
         
     | 
| 
      
 10 
     | 
    
         
            +
            redis.call('publish', namespace .. ':stats:' .. channel, id)
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            return id
         
     | 
| 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            local namespace, channel, cmd, value = ARGV[1], ARGV[2], ARGV[3], ARGV[4]
         
     | 
| 
      
 2 
     | 
    
         
            +
            local channel_key = namespace .. ':channel:' .. channel
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            local matched_keys = { channel_key }
         
     | 
| 
      
 5 
     | 
    
         
            +
            if string.find(channel_key, '*') then
         
     | 
| 
      
 6 
     | 
    
         
            +
              matched_keys = redis.call('keys', channel_key)
         
     | 
| 
      
 7 
     | 
    
         
            +
            end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            local purged = 0
         
     | 
| 
      
 10 
     | 
    
         
            +
            for i,key in ipairs(matched_keys) do
         
     | 
| 
      
 11 
     | 
    
         
            +
              if cmd == 'BEFORE' then
         
     | 
| 
      
 12 
     | 
    
         
            +
                purged = purged + redis.call('zremrangebyscore', key, '-inf', '(' .. value)
         
     | 
| 
      
 13 
     | 
    
         
            +
              elseif cmd == 'KEEP' then
         
     | 
| 
      
 14 
     | 
    
         
            +
                purged = purged + redis.call('zremrangebyrank', key, 0, 0 - (value - 1))
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            return purged
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,110 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: remq
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.1a
         
     | 
| 
      
 5 
     | 
    
         
            +
              prerelease: 5
         
     | 
| 
      
 6 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 7 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Evan Owen
         
     | 
| 
      
 9 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 10 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 11 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2012-07-01 00:00:00.000000000 Z
         
     | 
| 
      
 13 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 14 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 15 
     | 
    
         
            +
              name: redis
         
     | 
| 
      
 16 
     | 
    
         
            +
              requirement: &70122011105460 !ruby/object:Gem::Requirement
         
     | 
| 
      
 17 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 18 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 19 
     | 
    
         
            +
                - - ~>
         
     | 
| 
      
 20 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 21 
     | 
    
         
            +
                    version: 3.0.1
         
     | 
| 
      
 22 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 23 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 24 
     | 
    
         
            +
              version_requirements: *70122011105460
         
     | 
| 
      
 25 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 26 
     | 
    
         
            +
              name: multi_json
         
     | 
| 
      
 27 
     | 
    
         
            +
              requirement: &70122011104960 !ruby/object:Gem::Requirement
         
     | 
| 
      
 28 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 29 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 30 
     | 
    
         
            +
                - - ~>
         
     | 
| 
      
 31 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 32 
     | 
    
         
            +
                    version: '1.0'
         
     | 
| 
      
 33 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 34 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 35 
     | 
    
         
            +
              version_requirements: *70122011104960
         
     | 
| 
      
 36 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 37 
     | 
    
         
            +
              name: rake
         
     | 
| 
      
 38 
     | 
    
         
            +
              requirement: &70122011104580 !ruby/object:Gem::Requirement
         
     | 
| 
      
 39 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 40 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 41 
     | 
    
         
            +
                - - ! '>='
         
     | 
| 
      
 42 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 43 
     | 
    
         
            +
                    version: '0'
         
     | 
| 
      
 44 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 45 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 46 
     | 
    
         
            +
              version_requirements: *70122011104580
         
     | 
| 
      
 47 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 48 
     | 
    
         
            +
              name: rspec
         
     | 
| 
      
 49 
     | 
    
         
            +
              requirement: &70122011103980 !ruby/object:Gem::Requirement
         
     | 
| 
      
 50 
     | 
    
         
            +
                none: false
         
     | 
| 
      
 51 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 52 
     | 
    
         
            +
                - - ~>
         
     | 
| 
      
 53 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 54 
     | 
    
         
            +
                    version: '2.6'
         
     | 
| 
      
 55 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 56 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 57 
     | 
    
         
            +
              version_requirements: *70122011103980
         
     | 
| 
      
 58 
     | 
    
         
            +
            description: ! "    Remq is a Redis-based protocol for building fast, persistent\n
         
     | 
| 
      
 59 
     | 
    
         
            +
              \   pub/sub message queues.\n\n    The Remq protocol is defined by a collection
         
     | 
| 
      
 60 
     | 
    
         
            +
              of Lua scripts\n    (located at https://github.com/kainosnoema/remq) which effectively\n
         
     | 
| 
      
 61 
     | 
    
         
            +
              \   turn Redis into a capable message queue broker for fast inter-service\n    communication.
         
     | 
| 
      
 62 
     | 
    
         
            +
              The Remq Ruby client library is built on top of these\n    scripts, making it easy
         
     | 
| 
      
 63 
     | 
    
         
            +
              to build fast, persisted pub/sub message queues.\n"
         
     | 
| 
      
 64 
     | 
    
         
            +
            email: kainosnoema@gmail.com
         
     | 
| 
      
 65 
     | 
    
         
            +
            executables: []
         
     | 
| 
      
 66 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 67 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 68 
     | 
    
         
            +
            files:
         
     | 
| 
      
 69 
     | 
    
         
            +
            - .gitignore
         
     | 
| 
      
 70 
     | 
    
         
            +
            - .gitmodules
         
     | 
| 
      
 71 
     | 
    
         
            +
            - LICENSE
         
     | 
| 
      
 72 
     | 
    
         
            +
            - Readme.md
         
     | 
| 
      
 73 
     | 
    
         
            +
            - lib/remq.rb
         
     | 
| 
      
 74 
     | 
    
         
            +
            - lib/remq/coder.rb
         
     | 
| 
      
 75 
     | 
    
         
            +
            - lib/remq/multi_json_coder.rb
         
     | 
| 
      
 76 
     | 
    
         
            +
            - lib/remq/script.rb
         
     | 
| 
      
 77 
     | 
    
         
            +
            - lib/remq/version.rb
         
     | 
| 
      
 78 
     | 
    
         
            +
            - remq.gemspec
         
     | 
| 
      
 79 
     | 
    
         
            +
            - spec/remq_spec.rb
         
     | 
| 
      
 80 
     | 
    
         
            +
            - vendor/remq/Readme.md
         
     | 
| 
      
 81 
     | 
    
         
            +
            - vendor/remq/scripts/consume.lua
         
     | 
| 
      
 82 
     | 
    
         
            +
            - vendor/remq/scripts/publish.lua
         
     | 
| 
      
 83 
     | 
    
         
            +
            - vendor/remq/scripts/purge.lua
         
     | 
| 
      
 84 
     | 
    
         
            +
            homepage: http://github.com/kainosnoema/remq
         
     | 
| 
      
 85 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 86 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 87 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 88 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 89 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 90 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 91 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 92 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 93 
     | 
    
         
            +
              - - ! '>='
         
     | 
| 
      
 94 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 95 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 96 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 97 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 98 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 99 
     | 
    
         
            +
              - - ! '>'
         
     | 
| 
      
 100 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 101 
     | 
    
         
            +
                  version: 1.3.1
         
     | 
| 
      
 102 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 103 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 104 
     | 
    
         
            +
            rubygems_version: 1.8.11
         
     | 
| 
      
 105 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 106 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 107 
     | 
    
         
            +
            summary: A Remq client library for Ruby.
         
     | 
| 
      
 108 
     | 
    
         
            +
            test_files:
         
     | 
| 
      
 109 
     | 
    
         
            +
            - spec/remq_spec.rb
         
     | 
| 
      
 110 
     | 
    
         
            +
            has_rdoc: 
         
     |