disque 0.0.1.alpha → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/AUTHORS +2 -0
- data/README.md +28 -7
- data/disque.gemspec +5 -5
- data/lib/disque.rb +195 -2
- data/makefile +27 -0
- data/tests/disque_test.rb +237 -0
- metadata +12 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a89227c0d69455fc6897e54b94b06239d0e0ad56
|
4
|
+
data.tar.gz: a913c4b33ba3bee489f1a87a442675cb0b8d7128
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a64e05ec0ffe98863874e148b7ccad5ab34587b5edcf10a5f8dcef1149f67f0a595d96b2f25b61f472e50d53783c80eb66bdc29806280a07b53ac9f42e55a512
|
7
|
+
data.tar.gz: ff5b61bd64b033056a42d5f5d162fa7fb8c8e4a57e484f1aa7f91a344ad4a74941a0b8484618b4a8a39d4a47c6875b89c674e46e07667bb6799d5fe62ae8ba87
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/tmp
|
data/AUTHORS
ADDED
data/README.md
CHANGED
@@ -3,18 +3,39 @@ Disque.rb
|
|
3
3
|
|
4
4
|
Client for Disque, an in-memory, distributed job queue.
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
Usage
|
7
|
+
-----
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
Create a new Disque client by passing a list of nodes:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
client = Disque.new(["127.0.0.1:7711", "127.0.0.1:7712", "127.0.0.1:7713"])
|
13
|
+
```
|
14
|
+
|
15
|
+
Now you can add jobs:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
client.push("foo", "bar", 100)
|
19
|
+
```
|
20
|
+
|
21
|
+
It will push the job "bar" to the queue "foo" with a timeout of 100
|
22
|
+
ms, and return the id of the job if it was received and replicated
|
23
|
+
in time.
|
24
|
+
|
25
|
+
Then, your workers will do something like this:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
loop do
|
29
|
+
client.fetch(from: ["foo"]) do |job|
|
30
|
+
# Do something with `job`
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
13
34
|
|
14
35
|
Installation
|
15
36
|
------------
|
16
37
|
|
17
|
-
You
|
38
|
+
You can install it using rubygems.
|
18
39
|
|
19
40
|
```
|
20
41
|
$ gem install disque
|
data/disque.gemspec
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "disque"
|
5
|
-
s.version = "0.0.
|
5
|
+
s.version = "0.0.2"
|
6
6
|
s.summary = "Client for Disque"
|
7
|
-
s.description = "
|
8
|
-
s.authors = ["Michel Martens"]
|
9
|
-
s.email = ["michel@soveran.com"]
|
10
|
-
s.homepage = "https://github.com/soveran/disque
|
7
|
+
s.description = "Disque for Ruby"
|
8
|
+
s.authors = ["Michel Martens", "Damian Janowski"]
|
9
|
+
s.email = ["michel@soveran.com", "damian.janowski@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/soveran/disque-rb"
|
11
11
|
s.files = `git ls-files`.split("\n")
|
12
12
|
s.license = "MIT"
|
13
13
|
|
data/lib/disque.rb
CHANGED
@@ -1,7 +1,200 @@
|
|
1
1
|
require "redic"
|
2
2
|
|
3
3
|
class Disque
|
4
|
-
|
5
|
-
|
4
|
+
ECONN = [
|
5
|
+
Errno::ECONNREFUSED,
|
6
|
+
Errno::EINVAL,
|
7
|
+
]
|
8
|
+
|
9
|
+
attr :stats
|
10
|
+
attr :nodes
|
11
|
+
attr :prefix
|
12
|
+
|
13
|
+
# Create a new Disque client by passing a list of nodes.
|
14
|
+
#
|
15
|
+
# Disque.new(["127.0.0.1:7711", "127.0.0.1:7712", "127.0.0.1:7713"])
|
16
|
+
#
|
17
|
+
# For each operation, a counter is updated to signal which node was
|
18
|
+
# the originator of the message. Based on that information, after
|
19
|
+
# a full cycle (1000 operations, but configurable on initialization)
|
20
|
+
# the stats are checked to see what is the most convenient node
|
21
|
+
# to connect to in order to avoid extra jumps.
|
22
|
+
#
|
23
|
+
# TODO Account for authentication
|
24
|
+
# TODO Account for timeout
|
25
|
+
def initialize(hosts, cycle: 1000)
|
26
|
+
|
27
|
+
# Cycle length
|
28
|
+
@cycle = cycle
|
29
|
+
|
30
|
+
# Operations counter
|
31
|
+
@count = 0
|
32
|
+
|
33
|
+
# Known nodes
|
34
|
+
@nodes = Hash.new
|
35
|
+
|
36
|
+
# Connection stats
|
37
|
+
@stats = Hash.new(0)
|
38
|
+
|
39
|
+
# Main client
|
40
|
+
@client = Redic.new
|
41
|
+
|
42
|
+
# Scout client
|
43
|
+
@scout = Redic.new
|
44
|
+
|
45
|
+
# Preferred client prefix
|
46
|
+
@prefix = nil
|
47
|
+
|
48
|
+
explore!(hosts)
|
49
|
+
end
|
50
|
+
|
51
|
+
def url(host)
|
52
|
+
sprintf("disque://%s", host)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Collect the list of nodes by means of `CLUSTER NODES` and
|
56
|
+
# keep a connection to the node that provided that information.
|
57
|
+
def explore!(hosts)
|
58
|
+
|
59
|
+
# Reset nodes
|
60
|
+
@nodes.clear
|
61
|
+
|
62
|
+
hosts.each do |host|
|
63
|
+
begin
|
64
|
+
@scout.configure(url(host))
|
65
|
+
|
66
|
+
@scout.call("CLUSTER", "NODES").lines do |line|
|
67
|
+
id, host, flag = line.split
|
68
|
+
|
69
|
+
prefix = id[0,8]
|
70
|
+
|
71
|
+
if flag == "myself"
|
72
|
+
|
73
|
+
# Configure main client
|
74
|
+
@client.configure(@scout.url)
|
75
|
+
|
76
|
+
# Keep track of selected node
|
77
|
+
@prefix = prefix
|
78
|
+
end
|
79
|
+
|
80
|
+
@nodes[prefix] = host
|
81
|
+
end
|
82
|
+
|
83
|
+
@scout.quit
|
84
|
+
|
85
|
+
break
|
86
|
+
|
87
|
+
rescue *ECONN
|
88
|
+
$stderr.puts($!.inspect)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if @nodes.empty?
|
93
|
+
raise ArgumentError, "nodes unavailable"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def pick_client!
|
98
|
+
if @count == @cycle
|
99
|
+
@count = 0
|
100
|
+
prefix, _ = @stats.max { |a, b| a[1] <=> b[1] }
|
101
|
+
|
102
|
+
if prefix != @prefix
|
103
|
+
host = @nodes[prefix]
|
104
|
+
|
105
|
+
if host
|
106
|
+
|
107
|
+
# Reconfigure main client
|
108
|
+
@client.configure(url(host))
|
109
|
+
@prefix = prefix
|
110
|
+
|
111
|
+
# Reset stats for this new connection
|
112
|
+
@stats.clear
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Run commands on the active connection. If the
|
119
|
+
# connection is lost, new connections are tried
|
120
|
+
# until all nodes become unavailable.
|
121
|
+
def call(*args)
|
122
|
+
@client.call(*args)
|
123
|
+
rescue *ECONN
|
124
|
+
explore!(@nodes.values)
|
125
|
+
retry
|
126
|
+
end
|
127
|
+
|
128
|
+
# Disque's ADDJOB signature is as follows:
|
129
|
+
#
|
130
|
+
# ADDJOB queue_name job <ms-timeout>
|
131
|
+
# [REPLICATE <count>]
|
132
|
+
# [DELAY <sec>]
|
133
|
+
# [RETRY <sec>]
|
134
|
+
# [TTL <sec>]
|
135
|
+
# [MAXLEN <count>]
|
136
|
+
# [ASYNC]
|
137
|
+
#
|
138
|
+
# You can pass any optional arguments as a hash,
|
139
|
+
# for example:
|
140
|
+
#
|
141
|
+
# disque.push("foo", "myjob", 1000, ttl: 1, async: true)
|
142
|
+
#
|
143
|
+
# Note that `async` is a special case because it's just a
|
144
|
+
# flag. That's why `true` must be passed as its value.
|
145
|
+
def push(queue_name, job, ms_timeout, options = {})
|
146
|
+
command = ["ADDJOB", queue_name, job, ms_timeout]
|
147
|
+
command += options_to_arguments(options)
|
148
|
+
|
149
|
+
call(*command)
|
150
|
+
end
|
151
|
+
|
152
|
+
def fetch(from: [], count: 1, timeout: 0)
|
153
|
+
pick_client!
|
154
|
+
|
155
|
+
jobs = call(
|
156
|
+
"GETJOB",
|
157
|
+
"TIMEOUT", timeout,
|
158
|
+
"COUNT", count,
|
159
|
+
"FROM", *from)
|
160
|
+
|
161
|
+
if jobs then
|
162
|
+
@count += 1
|
163
|
+
|
164
|
+
jobs.each do |queue, msgid, job|
|
165
|
+
|
166
|
+
# Update stats
|
167
|
+
@stats[msgid[2,8]] += 1
|
168
|
+
|
169
|
+
if block_given?
|
170
|
+
|
171
|
+
# Process job
|
172
|
+
yield(job, queue)
|
173
|
+
|
174
|
+
# Remove job
|
175
|
+
call("ACKJOB", msgid)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
return jobs
|
181
|
+
end
|
182
|
+
|
183
|
+
def options_to_arguments(options)
|
184
|
+
arguments = []
|
185
|
+
|
186
|
+
options.each do |key, value|
|
187
|
+
if value == true
|
188
|
+
arguments.push(key)
|
189
|
+
else
|
190
|
+
arguments.push(key, value)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
return arguments
|
195
|
+
end
|
196
|
+
|
197
|
+
def quit
|
198
|
+
@client.quit
|
6
199
|
end
|
7
200
|
end
|
data/makefile
CHANGED
@@ -1,2 +1,29 @@
|
|
1
|
+
DIR?=./tmp
|
2
|
+
|
3
|
+
all: start test stop
|
4
|
+
|
1
5
|
test:
|
2
6
|
RUBYLIB=./lib cutest tests/*.rb
|
7
|
+
|
8
|
+
default:
|
9
|
+
@echo make \<port\>
|
10
|
+
@echo make \[start\|meet\|stop\|list\]
|
11
|
+
|
12
|
+
start: 7711 7712 7713
|
13
|
+
@disque -p 7712 CLUSTER MEET 127.0.0.1 7711 > /dev/null
|
14
|
+
@disque -p 7713 CLUSTER MEET 127.0.0.1 7712 > /dev/null
|
15
|
+
|
16
|
+
stop:
|
17
|
+
@kill `cat $(DIR)/disque.*.pid`
|
18
|
+
|
19
|
+
%:
|
20
|
+
@disque-server \
|
21
|
+
--port $@ \
|
22
|
+
--dir $(DIR) \
|
23
|
+
--daemonize yes \
|
24
|
+
--bind 127.0.0.1 \
|
25
|
+
--loglevel notice \
|
26
|
+
--pidfile disque.$@.pid \
|
27
|
+
--appendfilename disque.$@.aof \
|
28
|
+
--cluster-config-file disque.$@.nodes \
|
29
|
+
--logfile disque.$@.log
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require_relative "../lib/disque"
|
2
|
+
require "stringio"
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
module Silencer
|
6
|
+
@output = nil
|
7
|
+
|
8
|
+
def self.start
|
9
|
+
$olderr = $stderr
|
10
|
+
$stderr = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.stop
|
14
|
+
@output = $stderr.string
|
15
|
+
$stderr = $olderr
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.output
|
19
|
+
@output
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
DISQUE_NODES = [
|
24
|
+
"127.0.0.1:7710",
|
25
|
+
"127.0.0.1:7711",
|
26
|
+
"127.0.0.1:7712",
|
27
|
+
"127.0.0.1:7713",
|
28
|
+
]
|
29
|
+
|
30
|
+
DISQUE_BAD_NODES = DISQUE_NODES[0,1]
|
31
|
+
DISQUE_GOOD_NODES = DISQUE_NODES[1,3]
|
32
|
+
|
33
|
+
test "raise if connection is not possible" do
|
34
|
+
Silencer.start
|
35
|
+
assert_raise(ArgumentError) do
|
36
|
+
c = Disque.new(DISQUE_BAD_NODES)
|
37
|
+
end
|
38
|
+
Silencer.stop
|
39
|
+
|
40
|
+
assert_equal "#<Errno::ECONNREFUSED: Can't connect to: disque://127.0.0.1:7710>\n", Silencer.output
|
41
|
+
end
|
42
|
+
|
43
|
+
test "retry until a connection is reached" do
|
44
|
+
Silencer.start
|
45
|
+
c = Disque.new(DISQUE_NODES)
|
46
|
+
Silencer.stop
|
47
|
+
|
48
|
+
assert_equal "#<Errno::ECONNREFUSED: Can't connect to: disque://127.0.0.1:7710>\n", Silencer.output
|
49
|
+
assert_equal "PONG", c.call("PING")
|
50
|
+
end
|
51
|
+
|
52
|
+
test "lack of jobs" do
|
53
|
+
c = Disque.new(DISQUE_GOOD_NODES)
|
54
|
+
reached = false
|
55
|
+
|
56
|
+
c.fetch(from: ["foo"], timeout: 1) do |job|
|
57
|
+
reached = true
|
58
|
+
end
|
59
|
+
|
60
|
+
assert_equal false, reached
|
61
|
+
end
|
62
|
+
|
63
|
+
test "one job" do
|
64
|
+
c = Disque.new(DISQUE_GOOD_NODES)
|
65
|
+
|
66
|
+
c.push("foo", "bar", 1000)
|
67
|
+
|
68
|
+
c.fetch(from: ["foo"], count: 10) do |job, queue|
|
69
|
+
assert_equal "bar", job
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
test "multiple jobs" do
|
74
|
+
c = Disque.new(DISQUE_GOOD_NODES)
|
75
|
+
|
76
|
+
c.push("foo", "bar", 1000)
|
77
|
+
c.push("foo", "baz", 1000)
|
78
|
+
|
79
|
+
jobs = ["baz", "bar"]
|
80
|
+
|
81
|
+
c.fetch(from: ["foo"], count: 10) do |job, queue|
|
82
|
+
assert_equal jobs.pop, job
|
83
|
+
assert_equal "foo", queue
|
84
|
+
end
|
85
|
+
|
86
|
+
assert jobs.empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
test "multiple queues" do
|
90
|
+
c = Disque.new(DISQUE_GOOD_NODES)
|
91
|
+
|
92
|
+
c.push("foo", "bar", 1000)
|
93
|
+
c.push("qux", "baz", 1000)
|
94
|
+
|
95
|
+
queues = ["qux", "foo"]
|
96
|
+
jobs = ["baz", "bar"]
|
97
|
+
|
98
|
+
result = c.fetch(from: ["foo", "qux"], count: 10) do |job, queue|
|
99
|
+
assert_equal jobs.pop, job
|
100
|
+
assert_equal queues.pop, queue
|
101
|
+
end
|
102
|
+
|
103
|
+
assert jobs.empty?
|
104
|
+
assert queues.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
test "add jobs with other parameters" do
|
108
|
+
c = Disque.new(DISQUE_GOOD_NODES)
|
109
|
+
|
110
|
+
c.push("foo", "bar", 1000, async: true, ttl: 0)
|
111
|
+
|
112
|
+
sleep 0.1
|
113
|
+
|
114
|
+
queues = ["foo"]
|
115
|
+
jobs = ["bar"]
|
116
|
+
|
117
|
+
result = c.fetch(from: ["foo"], count: 10, timeout: 1) do |job, queue|
|
118
|
+
assert_equal jobs.pop, job
|
119
|
+
assert_equal queues.pop, queue
|
120
|
+
end
|
121
|
+
|
122
|
+
assert_equal ["bar"], jobs
|
123
|
+
assert_equal ["foo"], queues
|
124
|
+
end
|
125
|
+
|
126
|
+
test "connect to the best node" do
|
127
|
+
c1 = Disque.new([DISQUE_GOOD_NODES[0]], cycle: 2)
|
128
|
+
c2 = Disque.new([DISQUE_GOOD_NODES[1]], cycle: 2)
|
129
|
+
|
130
|
+
assert c1.prefix != c2.prefix
|
131
|
+
|
132
|
+
# Tamper stats to trigger a reconnection
|
133
|
+
c1.stats[c2.prefix] = 10
|
134
|
+
|
135
|
+
c1.push("q1", "j1", 1000)
|
136
|
+
c1.push("q1", "j2", 1000)
|
137
|
+
|
138
|
+
c2.push("q1", "j3", 1000)
|
139
|
+
|
140
|
+
c1.fetch(from: ["q1"])
|
141
|
+
c1.fetch(from: ["q1"])
|
142
|
+
c1.fetch(from: ["q1"])
|
143
|
+
|
144
|
+
# Client should have reconnected
|
145
|
+
assert c1.prefix == c2.prefix
|
146
|
+
end
|
147
|
+
|
148
|
+
test "connect to the best node, part 2" do
|
149
|
+
c1 = Disque.new([DISQUE_GOOD_NODES[0]], cycle: 2)
|
150
|
+
c2 = Disque.new([DISQUE_GOOD_NODES[1]], cycle: 2)
|
151
|
+
|
152
|
+
assert c1.prefix != c2.prefix
|
153
|
+
|
154
|
+
c1.push("q1", "j1", 0)
|
155
|
+
c1.push("q1", "j2", 0)
|
156
|
+
c1.push("q1", "j3", 0)
|
157
|
+
|
158
|
+
c2.fetch(from: ["q1"])
|
159
|
+
c2.fetch(from: ["q1"])
|
160
|
+
c2.fetch(from: ["q1"])
|
161
|
+
|
162
|
+
# Client should have reconnected
|
163
|
+
assert c1.prefix == c2.prefix
|
164
|
+
end
|
165
|
+
|
166
|
+
test "recover after node disconnection" do
|
167
|
+
c1 = Disque.new([DISQUE_GOOD_NODES[0]], cycle: 2)
|
168
|
+
|
169
|
+
prefix = c1.prefix
|
170
|
+
|
171
|
+
# Tamper stats to trigger a reconnection to a bad node
|
172
|
+
c1.stats["fake"] = 10
|
173
|
+
c1.nodes["fake"] = DISQUE_BAD_NODES[0]
|
174
|
+
|
175
|
+
# Delete the other nodes just in case
|
176
|
+
c1.nodes.delete_if do |key, val|
|
177
|
+
key != prefix &&
|
178
|
+
key != "fake"
|
179
|
+
end
|
180
|
+
|
181
|
+
c1.push("q1", "j1", 1000)
|
182
|
+
c1.push("q1", "j2", 1000)
|
183
|
+
c1.push("q1", "j3", 1000)
|
184
|
+
|
185
|
+
c1.fetch(from: ["q1"])
|
186
|
+
c1.fetch(from: ["q1"])
|
187
|
+
c1.fetch(from: ["q1"])
|
188
|
+
|
189
|
+
# Prefix should stay the same
|
190
|
+
assert prefix == c1.prefix
|
191
|
+
end
|
192
|
+
|
193
|
+
test "federation" do
|
194
|
+
c1 = Disque.new([DISQUE_GOOD_NODES[0]], cycle: 2)
|
195
|
+
c2 = Disque.new([DISQUE_GOOD_NODES[1]], cycle: 2)
|
196
|
+
|
197
|
+
c1.push("q1", "j1", 0)
|
198
|
+
|
199
|
+
c2.fetch(from: ["q1"], count: 10) do |job, queue|
|
200
|
+
assert_equal "j1", job
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
test "ack jobs when block is given" do
|
205
|
+
c = Disque.new(DISQUE_GOOD_NODES)
|
206
|
+
|
207
|
+
c.push("q1", "j1", 1000)
|
208
|
+
|
209
|
+
id = nil
|
210
|
+
|
211
|
+
_, id, _ = c.fetch(from: ["q1"]) { |*a| }[0]
|
212
|
+
|
213
|
+
assert id
|
214
|
+
|
215
|
+
info = Hash[*c.call("SHOW", id)]
|
216
|
+
|
217
|
+
if info.any?
|
218
|
+
|
219
|
+
# If the test runs too fast, we may get the job
|
220
|
+
# with the status set to "acked"
|
221
|
+
assert_equal "acked", info.fetch("state")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
test "don't ack jobs when no block is given" do
|
226
|
+
c = Disque.new(DISQUE_GOOD_NODES)
|
227
|
+
|
228
|
+
c.push("q1", "j1", 1000)
|
229
|
+
|
230
|
+
_, id, _ = c.fetch(from: ["q1"])[0]
|
231
|
+
|
232
|
+
assert id
|
233
|
+
|
234
|
+
info = Hash[*c.call("SHOW", id)]
|
235
|
+
|
236
|
+
assert_equal info.fetch("state"), "active"
|
237
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: disque
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michel Martens
|
8
|
+
- Damian Janowski
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2015-
|
12
|
+
date: 2015-04-27 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: redic
|
@@ -24,20 +25,24 @@ dependencies:
|
|
24
25
|
- - '>='
|
25
26
|
- !ruby/object:Gem::Version
|
26
27
|
version: '0'
|
27
|
-
description:
|
28
|
+
description: Disque for Ruby
|
28
29
|
email:
|
29
30
|
- michel@soveran.com
|
31
|
+
- damian.janowski@gmail.com
|
30
32
|
executables: []
|
31
33
|
extensions: []
|
32
34
|
extra_rdoc_files: []
|
33
35
|
files:
|
34
36
|
- .gems
|
37
|
+
- .gitignore
|
38
|
+
- AUTHORS
|
35
39
|
- LICENSE
|
36
40
|
- README.md
|
37
41
|
- disque.gemspec
|
38
42
|
- lib/disque.rb
|
39
43
|
- makefile
|
40
|
-
|
44
|
+
- tests/disque_test.rb
|
45
|
+
homepage: https://github.com/soveran/disque-rb
|
41
46
|
licenses:
|
42
47
|
- MIT
|
43
48
|
metadata: {}
|
@@ -52,12 +57,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
57
|
version: '0'
|
53
58
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
59
|
requirements:
|
55
|
-
- - '
|
60
|
+
- - '>='
|
56
61
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
62
|
+
version: '0'
|
58
63
|
requirements: []
|
59
64
|
rubyforge_project:
|
60
|
-
rubygems_version: 2.0.
|
65
|
+
rubygems_version: 2.0.3
|
61
66
|
signing_key:
|
62
67
|
specification_version: 4
|
63
68
|
summary: Client for Disque
|