sidekiq-status 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +2 -0
- data/Gemfile +4 -0
- data/README.md +53 -8
- data/lib/sidekiq-status.rb +39 -9
- data/lib/sidekiq-status/client_middleware.rb +1 -3
- data/lib/sidekiq-status/server_middleware.rb +12 -18
- data/lib/sidekiq-status/storage.rb +16 -8
- data/lib/sidekiq-status/version.rb +1 -1
- data/lib/sidekiq-status/worker.rb +20 -12
- data/sidekiq-status.gemspec +1 -0
- data/spec/lib/sidekiq-status/client_middleware_spec.rb +1 -0
- data/spec/lib/sidekiq-status/server_middleware_spec.rb +2 -0
- data/spec/lib/sidekiq-status_spec.rb +47 -11
- data/spec/spec_helper.rb +1 -1
- data/spec/support/test_jobs.rb +18 -3
- metadata +20 -2
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Sidekiq::Status
|
2
2
|
|
3
|
+
[![Code Climate](https://codeclimate.com/github/utgarda/sidekiq-status.png)](https://codeclimate.com/github/utgarda/sidekiq-status)
|
4
|
+
[![Build Status](https://secure.travis-ci.org/utgarda/sidekiq-status.png)](http://travis-ci.org/utgarda/sidekiq-status)
|
5
|
+
|
3
6
|
An extension to [Sidekiq](http://github.com/mperham/sidekiq) message processing to track your jobs. Inspired
|
4
7
|
by [resque-status](http://github.com/quirkey/resque-status) and mostly copying its features, using Sidekiq's middleware.
|
5
8
|
|
@@ -9,6 +12,8 @@ gem install sidekiq-status
|
|
9
12
|
|
10
13
|
## Usage
|
11
14
|
|
15
|
+
### Configuration
|
16
|
+
|
12
17
|
Configure your middleware chains, lookup [Middleware usage](https://github.com/mperham/sidekiq/wiki/Middleware)
|
13
18
|
on Sidekiq wiki for more info.
|
14
19
|
|
@@ -24,18 +29,16 @@ end
|
|
24
29
|
|
25
30
|
Sidekiq.configure_server do |config|
|
26
31
|
config.server_middleware do |chain|
|
27
|
-
chain.add Sidekiq::Status::ServerMiddleware
|
32
|
+
chain.add Sidekiq::Status::ServerMiddleware, expiration: 30.minutes # default
|
28
33
|
end
|
29
34
|
end
|
30
35
|
```
|
31
36
|
|
32
|
-
|
33
|
-
will be processed as usual.
|
37
|
+
After that you can use your jobs as usual and only include `Sidekiq::Status::Worker` module if you want additional functionality of tracking progress and passing any data from job to client.
|
34
38
|
|
35
39
|
``` ruby
|
36
40
|
class MyJob
|
37
41
|
include Sidekiq::Worker
|
38
|
-
include Sidekiq::Status::Worker
|
39
42
|
|
40
43
|
def perform(*args)
|
41
44
|
# your code goes here
|
@@ -43,19 +46,61 @@ class MyJob
|
|
43
46
|
end
|
44
47
|
```
|
45
48
|
|
49
|
+
### Retrieving status
|
50
|
+
|
46
51
|
Query for job status any time later:
|
47
52
|
|
48
53
|
``` ruby
|
49
54
|
job_id = MyJob.perform_async(*args)
|
50
|
-
#
|
51
|
-
status = Sidekiq::Status::
|
55
|
+
# :queued, :working, :complete or :failed , nil after expiry (30 minutes)
|
56
|
+
status = Sidekiq::Status::status(job_id)
|
57
|
+
Sidekiq::Status::queued? job_id
|
58
|
+
Sidekiq::Status::working? job_id
|
59
|
+
Sidekiq::Status::complete? job_id
|
60
|
+
Sidekiq::Status::failed? job_id
|
61
|
+
```
|
62
|
+
|
63
|
+
### Tracking progress, saving and retrieveing data associated with job
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
class MyJob
|
67
|
+
include Sidekiq::Worker
|
68
|
+
include Sidekiq::Status::Worker # Important!
|
69
|
+
|
70
|
+
def perform(*args)
|
71
|
+
# your code goes here
|
72
|
+
|
73
|
+
# the common idiom to track progress of your task
|
74
|
+
at 5, 100, "Almost done"
|
75
|
+
|
76
|
+
# a way to associate data with your job
|
77
|
+
store vino: 'veritas'
|
78
|
+
|
79
|
+
# a way of retrieving said data
|
80
|
+
# remember that retrieved data is always is String|nil
|
81
|
+
vino = retrieve :vino
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
job_id = MyJob.perform_async(*args)
|
86
|
+
data = Sidekiq::Status::get_all job_id
|
87
|
+
data # => {status: 'complete', update_time: 1360006573, vino: 'veritas'}
|
88
|
+
Sidekiq::Status::get job_id, :vino #=> 'veritas'
|
89
|
+
Sidekiq::Status::num job_id #=> 5
|
90
|
+
Sidekiq::Status::total job_id #=> 100
|
91
|
+
Sidekiq::Status::message job_id #=> "Almost done"
|
92
|
+
Sidekiq::Status::pct_complete job_id #=> 5
|
52
93
|
```
|
53
94
|
|
54
95
|
### Features coming
|
55
|
-
* Progress tracking, messages from running jobs
|
56
96
|
* Stopping jobs by id
|
57
97
|
* Minimal web UI
|
58
98
|
|
99
|
+
## Thanks
|
100
|
+
Andrew Korzhuev
|
101
|
+
Jon Moses
|
102
|
+
Wayne Hoover
|
103
|
+
|
59
104
|
## License
|
60
105
|
MIT License , see LICENSE for more details.
|
61
|
-
© 2012 Evgeniy Tsvigun
|
106
|
+
© 2012 - 2013 Evgeniy Tsvigun
|
data/lib/sidekiq-status.rb
CHANGED
@@ -4,24 +4,54 @@ require 'sidekiq-status/worker'
|
|
4
4
|
require 'sidekiq-status/client_middleware'
|
5
5
|
require 'sidekiq-status/server_middleware'
|
6
6
|
|
7
|
-
module Sidekiq
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
UUID_REGEXP = /[0-9A-F]{24}/i #RegEx for SecureRandom.hex(12) which is the format Sidekiq uses for its jid
|
7
|
+
module Sidekiq::Status
|
8
|
+
extend Storage
|
9
|
+
DEFAULT_EXPIRY = 60 * 30
|
10
|
+
STATUS = %w(queued working complete stopped failed).map(&:to_sym).freeze
|
12
11
|
|
12
|
+
class << self
|
13
13
|
# Job status by id
|
14
14
|
# @param [String] id job id returned by async_perform
|
15
|
-
# @return [String] job status, possible values
|
16
|
-
def
|
17
|
-
read_field_for_id(
|
15
|
+
# @return [String] job status, possible values are in STATUS
|
16
|
+
def get(job_id, field)
|
17
|
+
read_field_for_id(job_id, field)
|
18
18
|
end
|
19
19
|
|
20
20
|
# Get all status fields for a job
|
21
21
|
# @params [String] id job id returned by async_perform
|
22
22
|
# @return [Hash] hash of all fields stored for the job
|
23
|
-
def
|
23
|
+
def get_all(id)
|
24
24
|
read_hash_for_id(id)
|
25
25
|
end
|
26
|
+
|
27
|
+
def status(job_id)
|
28
|
+
status = get(job_id, :status)
|
29
|
+
status.to_sym unless status.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
STATUS.each do |name|
|
33
|
+
class_eval(<<-END, __FILE__, __LINE__)
|
34
|
+
def #{name}?(job_id)
|
35
|
+
status(job_id) == :#{name}
|
36
|
+
end
|
37
|
+
END
|
38
|
+
end
|
39
|
+
|
40
|
+
# Methods for retrieving job completion
|
41
|
+
def num(job_id)
|
42
|
+
get(job_id, :num).to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
def total(job_id)
|
46
|
+
get(job_id, :total).to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def pct_complete(job_id)
|
50
|
+
(num(job_id).to_f / total(job_id)) * 100
|
51
|
+
end
|
52
|
+
|
53
|
+
def message(job_id)
|
54
|
+
get(job_id, :message)
|
55
|
+
end
|
26
56
|
end
|
27
57
|
end
|
@@ -7,9 +7,7 @@ module Sidekiq::Status
|
|
7
7
|
# @param [Array] msg job arguments
|
8
8
|
# @param [String] queue the queue's name
|
9
9
|
def call(worker_class, msg, queue)
|
10
|
-
|
11
|
-
store_for_id(msg['jid'], :status => :queued)
|
12
|
-
end
|
10
|
+
store_status msg['jid'], :queued
|
13
11
|
yield
|
14
12
|
end
|
15
13
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module Sidekiq::Status
|
2
2
|
# Should be in the server middleware chain
|
3
3
|
class ServerMiddleware
|
4
|
+
include Storage
|
5
|
+
|
4
6
|
# Parameterized initialization, use it when adding middleware to server chain
|
5
7
|
# chain.add Sidekiq::Status::ServerMiddleware, :expiration => 60 * 5
|
6
8
|
# @param [Hash] opts middleware initialization options
|
7
9
|
# @option opts [Fixnum] :expiration ttl for complete jobs
|
8
|
-
def initialize(opts = {
|
10
|
+
def initialize(opts = {})
|
9
11
|
@expiration = opts[:expiration]
|
10
12
|
end
|
11
13
|
|
@@ -20,26 +22,18 @@ module Sidekiq::Status
|
|
20
22
|
# @param [Array] msg job args, should have jid format
|
21
23
|
# @param [String] queue queue name
|
22
24
|
def call(worker, msg, queue)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
worker.store 'status' => 'complete'
|
31
|
-
else
|
32
|
-
yield
|
33
|
-
end
|
25
|
+
# a way of overriding default expiration time,
|
26
|
+
# so worker wouldn't lose its data
|
27
|
+
worker.expiration = @expiration if worker.respond_to? :expiration
|
28
|
+
|
29
|
+
store_status worker.jid, :working, @expiration
|
30
|
+
yield
|
31
|
+
store_status worker.jid, :complete, @expiration
|
34
32
|
rescue Worker::Stopped
|
35
|
-
worker.
|
33
|
+
store_status worker.jid, :stopped, @expiration
|
36
34
|
rescue
|
37
|
-
|
38
|
-
worker.store 'status' => 'failed'
|
39
|
-
end
|
35
|
+
store_status worker.jid, :failed, @expiration
|
40
36
|
raise
|
41
|
-
ensure
|
42
|
-
Sidekiq.redis { |conn| conn.expire worker.id, @expiration } if worker.is_a? Worker
|
43
37
|
end
|
44
38
|
end
|
45
39
|
end
|
@@ -8,24 +8,32 @@ module Sidekiq::Status::Storage
|
|
8
8
|
# @param [String] id job id
|
9
9
|
# @param [Hash] status_updates updated values
|
10
10
|
# @return [String] Redis operation status code
|
11
|
-
def store_for_id(id, status_updates)
|
11
|
+
def store_for_id(id, status_updates, expiration = nil)
|
12
12
|
Sidekiq.redis do |conn|
|
13
|
-
|
14
|
-
conn.hmset
|
15
|
-
conn.expire id, Sidekiq::Status::DEFAULT_EXPIRY
|
13
|
+
conn.multi do
|
14
|
+
conn.hmset id, 'update_time', Time.now.to_i, *(status_updates.to_a.flatten(1))
|
15
|
+
conn.expire id, (expiration || Sidekiq::Status::DEFAULT_EXPIRY)
|
16
16
|
conn.publish "status_updates", id
|
17
|
-
end
|
18
|
-
answers[0]
|
17
|
+
end[0]
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
21
|
+
# Stores job status and sets expiration time to it
|
22
|
+
# only in case of :failed or :stopped job
|
23
|
+
# @param [String] id job id
|
24
|
+
# @param [Symbol] job status
|
25
|
+
# @return [String] Redis operation status code
|
26
|
+
def store_status(id, status, expiration = nil)
|
27
|
+
store_for_id id, {status: status}, expiration
|
28
|
+
end
|
29
|
+
|
22
30
|
# Gets a single valued from job status hash
|
23
31
|
# @param [String] id job id
|
24
32
|
# @param [String] Symbol field fetched field name
|
25
33
|
# @return [String] Redis operation status code
|
26
|
-
def read_field_for_id(
|
34
|
+
def read_field_for_id(id, field)
|
27
35
|
Sidekiq.redis do |conn|
|
28
|
-
conn.hmget(
|
36
|
+
conn.hmget(id, field)[0]
|
29
37
|
end
|
30
38
|
end
|
31
39
|
|
@@ -4,23 +4,31 @@ module Sidekiq::Status::Worker
|
|
4
4
|
class Stopped < StandardError
|
5
5
|
end
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
# Worker id initialization
|
10
|
-
# @param [String] id id generated on client-side
|
11
|
-
# @raise [RuntimeError] raised in case of second id initialization attempt
|
12
|
-
# @return [String] id
|
13
|
-
def id=(id)
|
14
|
-
raise RuntimeError("Worker ID is already set : #{@id}") if @id
|
15
|
-
@id=id
|
16
|
-
end
|
7
|
+
attr_accessor :expiration
|
17
8
|
|
18
9
|
# Stores multiple values into a job's status hash,
|
19
10
|
# sets last update time
|
20
11
|
# @param [Hash] status_updates updated values
|
21
12
|
# @return [String] Redis operation status code
|
22
13
|
def store(hash)
|
23
|
-
store_for_id
|
14
|
+
store_for_id @jid, hash, @expiration
|
15
|
+
end
|
16
|
+
|
17
|
+
# Read value from job status hash
|
18
|
+
# @param String|Symbol hask key
|
19
|
+
# @return [String]
|
20
|
+
def retrieve(name)
|
21
|
+
read_field_for_id @jid, name
|
22
|
+
end
|
23
|
+
|
24
|
+
# Sets current task progress
|
25
|
+
# (inspired by resque-status)
|
26
|
+
# @param Fixnum number of tasks done
|
27
|
+
# @param Fixnum total number of tasks
|
28
|
+
# @param String optional message
|
29
|
+
# @return [String]
|
30
|
+
def at(num, total, message=nil)
|
31
|
+
store({num: num, total: total, message: message})
|
24
32
|
end
|
25
33
|
|
26
|
-
end
|
34
|
+
end
|
data/sidekiq-status.gemspec
CHANGED
@@ -13,6 +13,7 @@ describe Sidekiq::Status::ClientMiddleware do
|
|
13
13
|
SecureRandom.should_receive(:hex).once.and_return(job_id)
|
14
14
|
StubJob.perform_async(:arg1 => 'val1').should == job_id
|
15
15
|
redis.hget(job_id, :status).should == 'queued'
|
16
|
+
Sidekiq::Status::queued?(job_id).should be_true
|
16
17
|
end
|
17
18
|
|
18
19
|
it "sets status hash ttl" do
|
@@ -21,6 +21,7 @@ describe Sidekiq::Status::ServerMiddleware do
|
|
21
21
|
job_id]
|
22
22
|
end
|
23
23
|
redis.hget(job_id, :status).should == 'complete'
|
24
|
+
Sidekiq::Status::complete?(job_id).should be_true
|
24
25
|
end
|
25
26
|
|
26
27
|
it "sets failed status" do
|
@@ -31,6 +32,7 @@ describe Sidekiq::Status::ServerMiddleware do
|
|
31
32
|
}.should == [job_id]*3
|
32
33
|
end
|
33
34
|
redis.hget(job_id, :status).should == 'failed'
|
35
|
+
Sidekiq::Status::failed?(job_id).should be_true
|
34
36
|
end
|
35
37
|
|
36
38
|
it "sets status hash ttl" do
|
@@ -11,17 +11,53 @@ describe Sidekiq::Status do
|
|
11
11
|
# so we should wait till they expire
|
12
12
|
before { redis.flushall; sleep 0.1 }
|
13
13
|
|
14
|
-
describe ".
|
15
|
-
it "gets job status by id" do
|
14
|
+
describe ".status, .working?, .complete?" do
|
15
|
+
it "gets job status by id as symbol" do
|
16
16
|
SecureRandom.should_receive(:hex).once.and_return(job_id)
|
17
17
|
|
18
18
|
start_server do
|
19
19
|
capture_status_updates(2) {
|
20
20
|
LongJob.perform_async(1).should == job_id
|
21
21
|
}.should == [job_id]*2
|
22
|
-
Sidekiq::Status.
|
22
|
+
Sidekiq::Status.status(job_id).should == :working
|
23
|
+
Sidekiq::Status.working?(job_id).should be_true
|
24
|
+
Sidekiq::Status::queued?(job_id).should be_false
|
25
|
+
Sidekiq::Status::failed?(job_id ).should be_false
|
26
|
+
Sidekiq::Status::complete?(job_id).should be_false
|
27
|
+
Sidekiq::Status::stopped?(job_id).should be_false
|
28
|
+
end
|
29
|
+
Sidekiq::Status.status(job_id).should == :complete
|
30
|
+
Sidekiq::Status.complete?(job_id).should be_true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".get" do
|
35
|
+
it "gets a single value from data hash as string" do
|
36
|
+
SecureRandom.should_receive(:hex).once.and_return(job_id)
|
37
|
+
|
38
|
+
start_server do
|
39
|
+
capture_status_updates(3) {
|
40
|
+
DataJob.perform_async.should == job_id
|
41
|
+
}.should == [job_id]*3
|
42
|
+
Sidekiq::Status.get(job_id, :status).should == 'working'
|
43
|
+
end
|
44
|
+
Sidekiq::Status.get(job_id, :data).should == 'meow'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".num, .total, .pct_complete, .message" do
|
49
|
+
it "should return job progress with correct type to it" do
|
50
|
+
SecureRandom.should_receive(:hex).once.and_return(job_id)
|
51
|
+
|
52
|
+
start_server do
|
53
|
+
capture_status_updates(3) {
|
54
|
+
ProgressJob.perform_async.should == job_id
|
55
|
+
}.should == [job_id]*3
|
23
56
|
end
|
24
|
-
Sidekiq::Status.
|
57
|
+
Sidekiq::Status.num(job_id).should == 100
|
58
|
+
Sidekiq::Status.total(job_id).should == 500
|
59
|
+
Sidekiq::Status.pct_complete(job_id).should == 20
|
60
|
+
Sidekiq::Status.message(job_id).should == 'howdy, partner?'
|
25
61
|
end
|
26
62
|
end
|
27
63
|
|
@@ -42,19 +78,19 @@ describe Sidekiq::Status do
|
|
42
78
|
end
|
43
79
|
|
44
80
|
context "keeps normal Sidekiq functionality" do
|
45
|
-
it "does jobs with and without
|
46
|
-
SecureRandom.should_receive(:hex).exactly(4).times.and_return(job_id, job_id_1)
|
81
|
+
it "does jobs with and without included worker module" do
|
82
|
+
SecureRandom.should_receive(:hex).exactly(4).times.and_return(job_id, job_id, job_id_1, job_id_1)
|
47
83
|
start_server do
|
48
|
-
capture_status_updates(
|
84
|
+
capture_status_updates(12) {
|
49
85
|
StubJob.perform_async.should == job_id
|
50
86
|
NoStatusConfirmationJob.perform_async(1)
|
51
87
|
StubJob.perform_async.should == job_id_1
|
52
88
|
NoStatusConfirmationJob.perform_async(2)
|
53
|
-
}.should =~ [job_id, job_id_1] *
|
89
|
+
}.should =~ [job_id, job_id_1] * 6
|
54
90
|
end
|
55
91
|
redis.mget('NoStatusConfirmationJob_1', 'NoStatusConfirmationJob_2').should == %w(done)*2
|
56
|
-
Sidekiq::Status.
|
57
|
-
Sidekiq::Status.
|
92
|
+
Sidekiq::Status.status(job_id).should == :complete
|
93
|
+
Sidekiq::Status.status(job_id_1).should == :complete
|
58
94
|
end
|
59
95
|
|
60
96
|
it "retries failed jobs" do
|
@@ -64,7 +100,7 @@ describe Sidekiq::Status do
|
|
64
100
|
RetriedJob.perform_async().should == job_id
|
65
101
|
}.should == [job_id] * 5
|
66
102
|
end
|
67
|
-
Sidekiq::Status.
|
103
|
+
Sidekiq::Status.status(job_id).should == :complete
|
68
104
|
end
|
69
105
|
end
|
70
106
|
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/test_jobs.rb
CHANGED
@@ -14,10 +14,26 @@ class LongJob < StubJob
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
class DataJob < StubJob
|
18
|
+
def perform
|
19
|
+
sleep 0.1
|
20
|
+
store({data: 'meow'})
|
21
|
+
retrieve(:data).should == 'meow'
|
22
|
+
sleep 0.1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ProgressJob < StubJob
|
27
|
+
def perform
|
28
|
+
at 100, 500, 'howdy, partner?'
|
29
|
+
sleep 0.1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
17
33
|
class ConfirmationJob < StubJob
|
18
34
|
def perform(*args)
|
19
35
|
Sidekiq.redis do |conn|
|
20
|
-
conn.publish "job_messages_#{
|
36
|
+
conn.publish "job_messages_#{jid}", "while in #perform, status = #{conn.hget jid, :status}"
|
21
37
|
end
|
22
38
|
end
|
23
39
|
end
|
@@ -28,7 +44,6 @@ class NoStatusConfirmationJob
|
|
28
44
|
Sidekiq.redis do |conn|
|
29
45
|
conn.set "NoStatusConfirmationJob_#{id}", "done"
|
30
46
|
end
|
31
|
-
|
32
47
|
end
|
33
48
|
end
|
34
49
|
|
@@ -42,7 +57,7 @@ class RetriedJob < StubJob
|
|
42
57
|
sidekiq_options 'retry' => 'true'
|
43
58
|
def perform()
|
44
59
|
Sidekiq.redis do |conn|
|
45
|
-
key = "RetriedJob_#{
|
60
|
+
key = "RetriedJob_#{jid}"
|
46
61
|
unless conn.exists key
|
47
62
|
conn.set key, 'tried'
|
48
63
|
raise StandardError
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-status
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sidekiq
|
@@ -27,6 +27,22 @@ dependencies:
|
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '2.7'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
30
46
|
- !ruby/object:Gem::Dependency
|
31
47
|
name: rspec
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +68,8 @@ extra_rdoc_files: []
|
|
52
68
|
files:
|
53
69
|
- .gitignore
|
54
70
|
- .rspec
|
71
|
+
- .travis.yml
|
72
|
+
- Gemfile
|
55
73
|
- LICENSE
|
56
74
|
- README.md
|
57
75
|
- Rakefile
|