krakow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## v0.0.1
2
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ krakow (0.1.0)
5
+ celluloid-io
6
+ http
7
+ multi_json
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ celluloid (0.15.2)
13
+ timers (~> 1.1.0)
14
+ celluloid-io (0.15.0)
15
+ celluloid (>= 0.15.0)
16
+ nio4r (>= 0.5.0)
17
+ http (0.5.0)
18
+ http_parser.rb
19
+ http_parser.rb (0.6.0)
20
+ multi_json (1.8.4)
21
+ nio4r (0.5.0)
22
+ timers (1.1.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ krakow!
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Krakow
2
+
3
+ "KRAKOW! KRAKOW! Two direct hits!"
4
+
5
+ ## Spiff
6
+
7
+ ```ruby
8
+ require 'krakow'
9
+
10
+ producer = Krakow::Producer(
11
+ :host => 'HOST',
12
+ :port => 'PORT',
13
+ :topic => 'target'
14
+ )
15
+ producer.write('direct hit!')
16
+ ```
17
+
18
+ ## Zargons
19
+
20
+ ```ruby
21
+ require 'krakow'
22
+
23
+ consumer = Krakow::Consumer(
24
+ :nsqlookupd => 'http://HOST:PORT',
25
+ :topic => 'target',
26
+ :channel => 'ship'
27
+ )
28
+
29
+ message = consumer.queue.pop
30
+ # do stuff
31
+ consumer.confirm(message.message_id)
32
+ ```
33
+
34
+ # Info
35
+ * Repo: https://github.com/chrisroberts/krakow
36
+ * IRC: Freenode @ spox
data/krakow.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
+ require 'krakow/version'
3
+ Gem::Specification.new do |s|
4
+ s.name = 'krakow'
5
+ s.version = Krakow::VERSION.version
6
+ s.summary = 'NSQ library'
7
+ s.author = 'Chris Roberts'
8
+ s.email = 'code@chrisroberts.org'
9
+ s.homepage = 'http://github.com/chrisroberts/krakow'
10
+ s.description = 'NSQ ruby library'
11
+ s.require_path = 'lib'
12
+ s.add_dependency 'celluloid-io'
13
+ s.add_dependency 'http'
14
+ s.add_dependency 'multi_json'
15
+ s.files = Dir['**/*']
16
+ end
@@ -0,0 +1,15 @@
1
+ module Krakow
2
+ class Command
3
+ class Cls < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ end
8
+
9
+ def to_line
10
+ "#{name}\n"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module Krakow
2
+ class Command
3
+ class Fin < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :message_id
8
+ end
9
+
10
+ def to_line
11
+ "#{name} #{message_id}\n"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'multi_json'
2
+
3
+ module Krakow
4
+ class Command
5
+ class Identify < Command
6
+
7
+ def initialize(args={})
8
+ super
9
+ required! :short_id, :long_id
10
+ optional(
11
+ :feature_negotiation, :heartbeat_interval, :output_buffer_size,
12
+ :output_buffer_timeout, :tls_v1, :snappy, :deflate, :deflate_level,
13
+ :sample_rate
14
+ )
15
+ end
16
+
17
+ def to_line
18
+ payload = MultiJson.dump(arguments)
19
+ [name, "\n", payload.length, payload].pack('a*a*l>a*')
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module Krakow
2
+ class Command
3
+ class Mpub < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :topic_name, :messages
8
+ end
9
+
10
+ def to_line
11
+ formatted_messages = messages.map do |message|
12
+ [message.length, message].pack('l>a*')
13
+ end.join
14
+ [name, ' ', topic_name, "\n", formatted_messages.length, messages.size, formatted_messages].pack('a*a*a*a*l>l>a*')
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module Krakow
2
+ class Command
3
+ class Nop < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ end
8
+
9
+ def to_line
10
+ "#{name}\n"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module Krakow
3
+ class Command
4
+ class Pub < Command
5
+
6
+ def initialize(args={})
7
+ super
8
+ required! :topic_name, :message
9
+ end
10
+
11
+ def to_line
12
+ [name, ' ', topic_name, "\n", message.length, message].pack('a*a*a*a*l>a*')
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Krakow
2
+ class Command
3
+ class Rdy < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :count
8
+ end
9
+
10
+ def to_line
11
+ "#{name} #{count}\n"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Krakow
2
+ class Command
3
+ class Req < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :message_id, :timeout
8
+ end
9
+
10
+ def to_line
11
+ "#{name} #{message_id} #{timeout}\n"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Krakow
2
+ class Command
3
+ class Sub < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :topic_name, :channel_name
8
+ end
9
+
10
+ def to_line
11
+ "#{name} #{topic_name} #{channel_name}\n"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Krakow
2
+ class Command
3
+ class Touch < Command
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :message_id
8
+ end
9
+
10
+ def to_line
11
+ "#{name} #{message_id}\n"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,25 @@
1
+ module Krakow
2
+ class Command
3
+
4
+ include Utils::Lazy
5
+
6
+ # Return command name
7
+ def name
8
+ self.class.name.split('::').last.upcase
9
+ end
10
+
11
+ # Convert to line output
12
+ def to_line
13
+ raise NoMethodError.new 'No line conversion method defined!'
14
+ end
15
+
16
+ # Make all the commands available
17
+ Dir.glob(File.join(File.dirname(__FILE__), 'command', '*')).each do |path|
18
+ autoload(
19
+ File.basename(path).sub(File.extname(path), '').capitalize.to_sym,
20
+ File.join('krakow/command', File.basename(path).sub(File.extname(path), ''))
21
+ )
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,89 @@
1
+ require 'celluloid/io'
2
+ require 'celluloid/autostart'
3
+
4
+ module Krakow
5
+ class Connection
6
+
7
+ include Utils::Lazy
8
+ include Celluloid::IO
9
+
10
+ finalizer :goodbye_my_love!
11
+
12
+ attr_reader :socket
13
+
14
+ def initialize(args={})
15
+ super
16
+ required! :host, :port
17
+ optional :version, :queue, :callback
18
+ arguments[:queue] ||= Queue.new
19
+ arguments[:version] ||= 'v2'
20
+ @socket = TCPSocket.new(host, port)
21
+ end
22
+
23
+ # Initialize the connection
24
+ def init!
25
+ socket.write version.rjust(4).upcase
26
+ async.process_to_queue!
27
+ end
28
+
29
+ # message:: Command instance to send
30
+ # Send the message
31
+ def transmit(message)
32
+ socket.write message.to_line
33
+ end
34
+
35
+ # Cleanup prior to destruction
36
+ def goodbye_my_love!
37
+ if(socket && !socket.closed?)
38
+ socket.write Command::Cls.new.to_line
39
+ socket.close
40
+ end
41
+ @socket = nil
42
+ end
43
+
44
+ # Receive message and return proper FrameType instance
45
+ def receive
46
+ buf = socket.read(8)
47
+ if(buf)
48
+ @receiving = true
49
+ struct = FrameType.decode(buf)
50
+ struct[:data] = socket.read(struct[:size])
51
+ @receiving = false
52
+ FrameType.build(struct)
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ # Currently in the process of receiving a message
59
+ def receiving?
60
+ !!@receiving
61
+ end
62
+
63
+ # Pull message and queue
64
+ def process_to_queue!
65
+ loop do
66
+ message = handle(receive)
67
+ if(message)
68
+ queue << message
69
+ end
70
+ end
71
+ end
72
+
73
+ # message:: FrameType instance
74
+ # Handle message if not an actual message
75
+ def handle(message)
76
+ # Grab heartbeats upfront
77
+ if(message.is_a?(FrameType::Response) && message.response == '_heartbeat_')
78
+ socket.write Command::Nop.new.to_line
79
+ nil
80
+ else
81
+ if(callback && callback[:actor] && callback[:method])
82
+ callback[:actor].send(callback[:method], message, current_actor)
83
+ else
84
+ message
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,101 @@
1
+ module Krakow
2
+ class Consumer
3
+
4
+ include Utils::Lazy
5
+ include Celluloid
6
+
7
+ finalizer :goodbye_my_love!
8
+
9
+ attr_reader :connections, :discovery, :queue
10
+
11
+ def initialize(args={})
12
+ super
13
+ required! :topic, :channel
14
+ optional :host, :port, :nslookupd, :receive_count
15
+ @connections = {}
16
+ @queue = Queue.new
17
+ if(nslookupd)
18
+ @discovery = Discovery.new(:nslookupd => nslookupd)
19
+ every(60){ init! }
20
+ init!
21
+ else
22
+ connection = build_connection(host, port, queue)
23
+ if(register(connection))
24
+ connections[:default] = connection
25
+ else
26
+ raise Error.new("Failed to establish subscription at provided end point (#{host}:#{port}")
27
+ end
28
+ end
29
+ end
30
+
31
+ def goodbye_my_love!
32
+ connections.values.each do |con|
33
+ con.terminate
34
+ end
35
+ end
36
+
37
+ def build_connection(host, port, queue)
38
+ connection = Connection.new(
39
+ :host => host,
40
+ :port => port,
41
+ :queue => queue
42
+ )
43
+ end
44
+
45
+ def process_message(message, connection)
46
+ puts 'PROCESSING!'
47
+ if(message.is_a?(FrameType::Message))
48
+ connection.transmit(Command::Rdy.new(:count => receive_count || 1))
49
+ end
50
+ message
51
+ end
52
+
53
+ # Requests lookup and adds connections
54
+ def init!
55
+ found = discovery.lookup(topic)
56
+ found.each do |node|
57
+ unless(connections[node[:hostname]])
58
+ connection = build_connection(node[:broadcast_address], node[:tcp_port], queue)
59
+ connections[node[:hostname]] = connection if register(connection)
60
+ end
61
+ end
62
+ end
63
+
64
+ # connection:: Connection
65
+ # Registers connection with subscription. Returns false if failed
66
+ def register(connection)
67
+ connection.init!
68
+ connection.transmit(Command::Sub.new(:topic_name => topic, :channel_name => channel))
69
+ unless(connection.queue.pop.is_a?(FrameType::Error))
70
+ connection.transmit(Command::Rdy.new(:count => receive_count || 1))
71
+ true
72
+ else
73
+ connection.terminate
74
+ false
75
+ end
76
+ end
77
+
78
+ # message_id:: Message ID
79
+ # Confirm message has been processed
80
+ def confirm(message_id)
81
+ writer.transmit(Command::Fin.new(:message_id => message_id))
82
+ writer.transmit(Command::Rdy.new(:count => (receive_count - queue.size) + 1))
83
+ true
84
+ end
85
+
86
+ # message_id:: Message ID
87
+ # timeout:: Requeue timeout (default is none)
88
+ # Requeue message (processing failure)
89
+ def requeue(message_id, timeout=0)
90
+ writer.transmit(Command::Req.new(:message_id => message_id, :timeout => timeout))
91
+ end
92
+
93
+ # Attempt to return free connection from pool for writing
94
+ def writer
95
+ connections.values.detect do |con|
96
+ !con.receiving?
97
+ end || connections.values.first
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,41 @@
1
+ require 'uri'
2
+ require 'http'
3
+ require 'multi_json'
4
+
5
+ module Krakow
6
+ class Discovery
7
+
8
+ include Utils::Lazy
9
+
10
+ def initialize(args={})
11
+ super
12
+ required! :nslookupd
13
+ end
14
+
15
+ # topic:: Topic name
16
+ # Return list of end points with given topic name available
17
+ def lookup(topic)
18
+ [nslookupd].flatten.map do |location|
19
+ uri = URI.parse(location)
20
+ uri.path = '/lookup'
21
+ uri.query = "topic=#{topic}&ts=#{Time.now.to_i}"
22
+ begin
23
+ content = HTTP.with(:accept => 'application/octet-stream').get(uri.to_s)
24
+ unless(content.respond_to?(:to_hash))
25
+ data = MultiJson.load(content.to_s)
26
+ else
27
+ data = content.to_hash
28
+ end
29
+ if(data['data'] && data['data']['producers'])
30
+ data['data']['producers'].map do |producer|
31
+ Hash[*producer.map{|k,v| [k.to_sym, v]}.flatten]
32
+ end
33
+ end
34
+ rescue => e
35
+ nil
36
+ end
37
+ end.compact.flatten(1).uniq
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,9 @@
1
+ module Krakow
2
+ class Error < StandardError
3
+
4
+ class BadResponse < Error
5
+ attr_accessor :result
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Krakow
2
+ class FrameType
3
+ class Error < FrameType
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :error
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Krakow
2
+ class FrameType
3
+ class Message < FrameType
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :attempts, :timestamp, :message_id, :message
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Krakow
2
+ class FrameType
3
+ class Response < FrameType
4
+
5
+ def initialize(args={})
6
+ super
7
+ required! :response
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ module Krakow
2
+ class FrameType
3
+
4
+ autoload :Error, 'krakow/frame_type/error'
5
+ autoload :Message, 'krakow/frame_type/message'
6
+ autoload :Response, 'krakow/frame_type/response'
7
+
8
+ include Utils::Lazy
9
+
10
+ FRAME_TYPE_MAP = [
11
+ FrameType::Response,
12
+ FrameType::Error,
13
+ FrameType::Message
14
+ ]
15
+ SIZE_BYTES = 4
16
+
17
+ class << self
18
+
19
+ # bytes:: 8 bytes
20
+ # Return information about incoming frame
21
+ def decode(bytes)
22
+ size, type = bytes.unpack('l>l>')
23
+ {:size => size - SIZE_BYTES, :type => type}
24
+ end
25
+
26
+ # args:: arguments (:type, :data, :size)
27
+ # Build proper FrameType instance based on args
28
+ def build(args={})
29
+ klass = FRAME_TYPE_MAP[args[:type].to_i]
30
+ if(klass == FrameType::Response)
31
+ klass.new(:response => args[:data])
32
+ elsif(klass == FrameType::Error)
33
+ klass.new(:error => args[:data])
34
+ elsif(klass == FrameType::Message)
35
+ unpacked = args[:data].unpack("Q>s>a16a#{args[:size]}")
36
+ klass.new(
37
+ Hash[*([:timestamp, :attempts, :message_id, :message].zip(unpacked).flatten)]
38
+ )
39
+ else
40
+ raise TypeError.new "Unknown frame type received: #{args[:type].inspect} - #{klass.inspect}"
41
+ end
42
+ end
43
+ end
44
+
45
+ def initialize(args={})
46
+ super
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,61 @@
1
+ module Krakow
2
+ class Producer
3
+
4
+ include Utils::Lazy
5
+ include Celluloid
6
+
7
+ finalizer :goodbye_my_love!
8
+
9
+ attr_reader :connection
10
+
11
+ def initialize(args={})
12
+ super
13
+ required! :host, :port, :topic
14
+ @connection = Connection.new(:host => host, :port => port)
15
+ connection.init!
16
+ end
17
+
18
+ def goodbye_my_love!
19
+ if(connection)
20
+ connection.terminate
21
+ end
22
+ @connection = nil
23
+ end
24
+
25
+ # message:: Message to send
26
+ # Write message
27
+ def write(*message)
28
+ if(message.size > 1)
29
+ connection.transmit(
30
+ Command::Mpub.new(
31
+ :topic_name => topic,
32
+ :messages => message
33
+ )
34
+ )
35
+ else
36
+ connection.transmit(
37
+ Command::Pub.new(
38
+ :message => message.first,
39
+ :topic_name => topic
40
+ )
41
+ )
42
+ end
43
+ read(:validate)
44
+ end
45
+
46
+ # args:: Options (:validate)
47
+ # Read response from connection. If :validate is included an
48
+ # exception will be raised if `FrameType::Error` is received
49
+ def read(*args)
50
+ result = connection.queue.pop
51
+ if(args.include?(:validate) && result.is_a?(FrameType::Error))
52
+ error = Error::BadResponse.new('Write failed')
53
+ error.result = result
54
+ abort error
55
+ else
56
+ result
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,52 @@
1
+ module Krakow
2
+ module Utils
3
+ module Lazy
4
+ attr_reader :arguments
5
+
6
+ def initialize(args={})
7
+ @arguments = {}.tap do |hash|
8
+ args.each do |k,v|
9
+ hash[k.to_sym] = v
10
+ end
11
+ end
12
+ end
13
+
14
+ # args:: list of required keys
15
+ # Check that required keys exist in `arguments` hash. Raise
16
+ # error if not found
17
+ def required!(*args)
18
+ args.each do |key|
19
+ unless(arguments.has_key?(key.to_sym))
20
+ raise ArgumentError.new "Missing required option `#{key}`!"
21
+ end
22
+ end
23
+ end
24
+
25
+ # args:: list of required keys
26
+ # Optional keys for arguments
27
+ def optional(*args)
28
+ args.each do |key|
29
+ key = key.to_sym
30
+ unless(arguments.has_key?(key))
31
+ arguments[key] = nil
32
+ end
33
+ end
34
+ end
35
+
36
+ def method_missing(*args)
37
+ key = args.first.to_sym
38
+ if(arguments.has_key?(key))
39
+ arguments[key]
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ def respond_to_missing?(key, *args)
46
+ key = key.to_sym
47
+ super || arguments.has_key?(key)
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ module Krakow
2
+ module Utils
3
+ autoload :Lazy, 'krakow/utils/lazy'
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'krakow'
2
+
3
+ module Krakow
4
+ class Version < Gem::Version
5
+ end
6
+ VERSION = Version.new('0.0.1')
7
+ end
data/lib/krakow.rb ADDED
@@ -0,0 +1,15 @@
1
+ autoload :Celluloid, 'celluloid'
2
+
3
+ module Krakow
4
+
5
+ autoload :Command, 'krakow/command'
6
+ autoload :Connection, 'krakow/connection'
7
+ autoload :Consumer, 'krakow/consumer'
8
+ autoload :Discovery, 'krakow/discovery'
9
+ autoload :Error, 'krakow/exceptions'
10
+ autoload :FrameType, 'krakow/frame_type'
11
+ autoload :Producer, 'krakow/producer'
12
+ autoload :Utils, 'krakow/utils'
13
+ autoload :Version, 'krakow/version'
14
+
15
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: krakow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Roberts
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-01-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: celluloid-io
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: http
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: multi_json
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: NSQ ruby library
63
+ email: code@chrisroberts.org
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - lib/krakow.rb
69
+ - lib/krakow/consumer.rb
70
+ - lib/krakow/frame_type.rb
71
+ - lib/krakow/command.rb
72
+ - lib/krakow/version.rb
73
+ - lib/krakow/utils/lazy.rb
74
+ - lib/krakow/producer.rb
75
+ - lib/krakow/frame_type/response.rb
76
+ - lib/krakow/frame_type/error.rb
77
+ - lib/krakow/frame_type/message.rb
78
+ - lib/krakow/command/pub.rb
79
+ - lib/krakow/command/cls.rb
80
+ - lib/krakow/command/touch.rb
81
+ - lib/krakow/command/fin.rb
82
+ - lib/krakow/command/identify.rb
83
+ - lib/krakow/command/req.rb
84
+ - lib/krakow/command/nop.rb
85
+ - lib/krakow/command/mpub.rb
86
+ - lib/krakow/command/sub.rb
87
+ - lib/krakow/command/rdy.rb
88
+ - lib/krakow/connection.rb
89
+ - lib/krakow/exceptions.rb
90
+ - lib/krakow/utils.rb
91
+ - lib/krakow/discovery.rb
92
+ - Gemfile
93
+ - README.md
94
+ - krakow.gemspec
95
+ - CHANGELOG.md
96
+ - Gemfile.lock
97
+ homepage: http://github.com/chrisroberts/krakow
98
+ licenses: []
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 1.8.24
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: NSQ library
121
+ test_files: []
122
+ has_rdoc: