sidekiq-status 0.2.0 → 0.3.0
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/.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
|
+
[](https://codeclimate.com/github/utgarda/sidekiq-status)
|
4
|
+
[](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
|