em-timers 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README +39 -0
- data/lib/em-timers.rb +8 -0
- data/lib/em-timers/cron_line.rb +251 -0
- data/lib/em-timers/em-timers.rb +164 -0
- data/lib/em-timers/numericmixable.rb +56 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fd2dd3c97deb96f7c3d9f2681bc9d7a8f03e5431
|
4
|
+
data.tar.gz: aa73bcc16297daddf064b76ec2cb84815d3d7f10
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 41c3dc4a4a28c1e8dcc790b5a7410e69d02bb18a08da38dbdb4e2f172987b8f3d5b6265e52070aa605245fb7eb8ce2fce414621d825a4d5071a4cde273550456
|
7
|
+
data.tar.gz: 42a47c94053fef639e74d7d952dce3a217a98ceff71f65e1231f5a56d5384481c29983435a8303e9f3b6c26525195388aee25c11bf8d2f4aff4e5c5fb637a6eb
|
data/README
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
em-timers
|
2
|
+
|
3
|
+
helper methods for timers in EventMachine
|
4
|
+
|
5
|
+
examples:
|
6
|
+
|
7
|
+
# Once per hour, starting now. Note the :now symbol must be passed for your block to be called immediately.
|
8
|
+
EM::Timers.do_hourly(:starting => :now) { puts "drink a beer" }
|
9
|
+
|
10
|
+
# Once per day, starting in 1 day.
|
11
|
+
EM::Timers.do_daily { puts "take a shower" }
|
12
|
+
|
13
|
+
# Once per week, starting in 10 hours
|
14
|
+
EM::Timers.do_weekly(:starting => 10.hours.from_now) { puts "take out the garbage" }
|
15
|
+
|
16
|
+
# Once per month, starting at this time tomorrow.
|
17
|
+
EM::Timers.do_monthly(:starting => 1.day.from_now) { puts "pay the bills" }
|
18
|
+
|
19
|
+
# Or you can simply pass a number of seconds til starting
|
20
|
+
EM::Timers.do_monthly(:starting => 2592000) { puts "pay the bills, in seconds!" }
|
21
|
+
|
22
|
+
# Leveraging the Chronic time parsing library. em-timers knows if you have required
|
23
|
+
# Chronic and uses it to parse strings if you have. Otherwise, it uses Time.parse.
|
24
|
+
#
|
25
|
+
# Every 2 hours, starting next Tuesday at 9AM.
|
26
|
+
require 'chronic'
|
27
|
+
EM::Timers.do(:every => 2.hours, :starting => 'next tuesday at 9am') { puts "go pee" }
|
28
|
+
|
29
|
+
# You can add up units of time as well
|
30
|
+
EM::Timers.do(:every => 1.minutes + 3.seconds) { puts "hi!" }
|
31
|
+
|
32
|
+
# You can list and cancel timers, too
|
33
|
+
EM::Timers.list.each { |timer|
|
34
|
+
timer.cancel
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
jakecdouglas@gmail.com
|
39
|
+
yakischloba on Freenode #eventmachine
|
data/lib/em-timers.rb
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006-2009, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
|
26
|
+
module EventMachine
|
27
|
+
|
28
|
+
module Timers
|
29
|
+
|
30
|
+
#
|
31
|
+
# A 'cron line' is a line in the sense of a crontab
|
32
|
+
# (man 5 crontab) file line.
|
33
|
+
#
|
34
|
+
class CronLine
|
35
|
+
|
36
|
+
#
|
37
|
+
# The string used for creating this cronline instance.
|
38
|
+
#
|
39
|
+
attr_reader :original
|
40
|
+
|
41
|
+
attr_reader \
|
42
|
+
:seconds,
|
43
|
+
:minutes,
|
44
|
+
:hours,
|
45
|
+
:days,
|
46
|
+
:months,
|
47
|
+
:weekdays
|
48
|
+
|
49
|
+
def initialize (line)
|
50
|
+
|
51
|
+
super()
|
52
|
+
|
53
|
+
@original = line
|
54
|
+
|
55
|
+
items = line.split
|
56
|
+
|
57
|
+
unless items.length == 5 or items.length == 6
|
58
|
+
raise(
|
59
|
+
"cron '#{line}' string should hold 5 or 6 items, not #{items.length}")
|
60
|
+
end
|
61
|
+
|
62
|
+
offset = items.length - 5
|
63
|
+
|
64
|
+
@seconds = offset == 1 ? parse_item(items[0], 0, 59) : [ 0 ]
|
65
|
+
@minutes = parse_item(items[0 + offset], 0, 59)
|
66
|
+
@hours = parse_item(items[1 + offset], 0, 24)
|
67
|
+
@days = parse_item(items[2 + offset], 1, 31)
|
68
|
+
@months = parse_item(items[3 + offset], 1, 12)
|
69
|
+
@weekdays = parse_weekdays(items[4 + offset])
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Returns true if the given time matches this cron line.
|
74
|
+
#
|
75
|
+
def matches? (time)
|
76
|
+
|
77
|
+
time = Time.at(time) unless time.kind_of?(Time)
|
78
|
+
|
79
|
+
return false unless sub_match?(time.sec, @seconds)
|
80
|
+
return false unless sub_match?(time.min, @minutes)
|
81
|
+
return false unless sub_match?(time.hour, @hours)
|
82
|
+
return false unless sub_match?(time.day, @days)
|
83
|
+
return false unless sub_match?(time.month, @months)
|
84
|
+
return false unless sub_match?(time.wday, @weekdays)
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Returns an array of 6 arrays (seconds, minutes, hours, days,
|
90
|
+
# months, weekdays).
|
91
|
+
# This method is used by the cronline unit tests.
|
92
|
+
#
|
93
|
+
def to_array
|
94
|
+
|
95
|
+
[ @seconds, @minutes, @hours, @days, @months, @weekdays ]
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Returns the next time that this cron line is supposed to 'fire'
|
100
|
+
#
|
101
|
+
# This is raw, 3 secs to iterate over 1 year on my macbook :( brutal.
|
102
|
+
#
|
103
|
+
# This method accepts an optional Time parameter. It's the starting point
|
104
|
+
# for the 'search'. By default, it's Time.now
|
105
|
+
#
|
106
|
+
# Note that the time instance returned will be in the same time zone that
|
107
|
+
# the given start point Time (thus a result in the local time zone will
|
108
|
+
# be passed if no start time is specified (search start time set to
|
109
|
+
# Time.now))
|
110
|
+
#
|
111
|
+
# >> EventMachine::Timers::CronLine.new('30 7 * * *').next_time( Time.mktime(2008,10,24,7,29) )
|
112
|
+
# => Fri Oct 24 07:30:00 -0500 2008
|
113
|
+
#
|
114
|
+
# >> EventMachine::Timers::CronLine.new('30 7 * * *').next_time( Time.utc(2008,10,24,7,29) )
|
115
|
+
# => Fri Oct 24 07:30:00 UTC 2008
|
116
|
+
#
|
117
|
+
# >> EventMachine::Timers::CronLine.new('30 7 * * *').next_time( Time.utc(2008,10,24,7,29) ).localtime
|
118
|
+
# => Fri Oct 24 02:30:00 -0500 2008
|
119
|
+
#
|
120
|
+
# (Thanks to K Liu for the note and the examples)
|
121
|
+
#
|
122
|
+
def next_time (time=Time.now)
|
123
|
+
|
124
|
+
time -= time.usec * 1e-6
|
125
|
+
time += 1
|
126
|
+
|
127
|
+
loop do
|
128
|
+
|
129
|
+
unless date_match?(time)
|
130
|
+
time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
|
131
|
+
next
|
132
|
+
end
|
133
|
+
|
134
|
+
unless sub_match?(time.hour, @hours)
|
135
|
+
time += (60 - time.min) * 60 - time.sec
|
136
|
+
next
|
137
|
+
end
|
138
|
+
|
139
|
+
unless sub_match?(time.min, @minutes)
|
140
|
+
time += 60 - time.sec
|
141
|
+
next
|
142
|
+
end
|
143
|
+
|
144
|
+
unless sub_match?(time.sec, @seconds)
|
145
|
+
time += 1
|
146
|
+
next
|
147
|
+
end
|
148
|
+
|
149
|
+
break
|
150
|
+
end
|
151
|
+
|
152
|
+
time
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
WDS = %w[ sun mon tue wed thu fri sat ]
|
158
|
+
#
|
159
|
+
# used by parse_weekday()
|
160
|
+
|
161
|
+
def parse_weekdays (item)
|
162
|
+
|
163
|
+
item = item.downcase
|
164
|
+
|
165
|
+
WDS.each_with_index { |day, index| item = item.gsub(day, index.to_s) }
|
166
|
+
|
167
|
+
r = parse_item(item, 0, 7)
|
168
|
+
|
169
|
+
r.is_a?(Array) ?
|
170
|
+
r.collect { |e| e == 7 ? 0 : e }.uniq :
|
171
|
+
r
|
172
|
+
end
|
173
|
+
|
174
|
+
def parse_item (item, min, max)
|
175
|
+
|
176
|
+
return nil if item == '*'
|
177
|
+
return parse_list(item, min, max) if item.index(',')
|
178
|
+
return parse_range(item, min, max) if item.index('*') or item.index('-')
|
179
|
+
|
180
|
+
i = Integer(item)
|
181
|
+
|
182
|
+
i = min if i < min
|
183
|
+
i = max if i > max
|
184
|
+
|
185
|
+
[ i ]
|
186
|
+
end
|
187
|
+
|
188
|
+
def parse_list (item, min, max)
|
189
|
+
|
190
|
+
item.split(',').inject([]) { |r, i|
|
191
|
+
r.push(parse_range(i, min, max))
|
192
|
+
}.flatten
|
193
|
+
end
|
194
|
+
|
195
|
+
def parse_range (item, min, max)
|
196
|
+
|
197
|
+
i = item.index('-')
|
198
|
+
j = item.index('/')
|
199
|
+
|
200
|
+
return item.to_i if (not i and not j)
|
201
|
+
|
202
|
+
inc = j ? Integer(item[j+1..-1]) : 1
|
203
|
+
|
204
|
+
istart = -1
|
205
|
+
iend = -1
|
206
|
+
|
207
|
+
if i
|
208
|
+
|
209
|
+
istart = Integer(item[0..i - 1])
|
210
|
+
|
211
|
+
if j
|
212
|
+
iend = Integer(item[i + 1..j])
|
213
|
+
else
|
214
|
+
iend = Integer(item[i + 1..-1])
|
215
|
+
end
|
216
|
+
|
217
|
+
else # case */x
|
218
|
+
|
219
|
+
istart = min
|
220
|
+
iend = max
|
221
|
+
end
|
222
|
+
|
223
|
+
istart = min if istart < min
|
224
|
+
iend = max if iend > max
|
225
|
+
|
226
|
+
result = []
|
227
|
+
|
228
|
+
value = istart
|
229
|
+
loop do
|
230
|
+
result << value
|
231
|
+
value = value + inc
|
232
|
+
break if value > iend
|
233
|
+
end
|
234
|
+
|
235
|
+
result
|
236
|
+
end
|
237
|
+
|
238
|
+
def sub_match?(value, values)
|
239
|
+
values.nil? || values.include?(value)
|
240
|
+
end
|
241
|
+
|
242
|
+
def date_match?(date)
|
243
|
+
return false unless sub_match?(date.day, @days)
|
244
|
+
return false unless sub_match?(date.month, @months)
|
245
|
+
return false unless sub_match?(date.wday, @weekdays)
|
246
|
+
true
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module EventMachine
|
2
|
+
|
3
|
+
module Timers
|
4
|
+
|
5
|
+
class Timer
|
6
|
+
@list = []
|
7
|
+
class << self
|
8
|
+
attr :list, false
|
9
|
+
end
|
10
|
+
attr :tag, false
|
11
|
+
attr :name, false
|
12
|
+
attr :options, true
|
13
|
+
attr :reschedule_timer, false
|
14
|
+
attr :kickoff_timer, false
|
15
|
+
def initialize(options, block)
|
16
|
+
@options = options
|
17
|
+
if options[:tag]
|
18
|
+
@tag = options[:tag]
|
19
|
+
end
|
20
|
+
if options[:name]
|
21
|
+
@name = options[:name]
|
22
|
+
end
|
23
|
+
@block = block
|
24
|
+
@repeats = true
|
25
|
+
Timer.list << self
|
26
|
+
end
|
27
|
+
def cancel
|
28
|
+
if @kickoff_timer
|
29
|
+
EM::cancel_timer(@kickoff_timer)
|
30
|
+
@kickoff_timer = nil
|
31
|
+
end
|
32
|
+
if @reschedule_timer
|
33
|
+
EM::cancel_timer(@reschedule_timer)
|
34
|
+
@reschedule_timer = nil
|
35
|
+
end
|
36
|
+
Timer.list.delete(self)
|
37
|
+
end
|
38
|
+
def schedule
|
39
|
+
increment = @options[:every] || 1
|
40
|
+
starting = @options[:starting]
|
41
|
+
|
42
|
+
if options[:cron]
|
43
|
+
starting =
|
44
|
+
EventMachine::Timers::CronLine.new(options[:cron]).next_time
|
45
|
+
@kickoff_timer = EM.add_timer(calculate_delta_time(starting)) {
|
46
|
+
@kickoff_timer = nil
|
47
|
+
run_and_reschedule_cron
|
48
|
+
}
|
49
|
+
return self
|
50
|
+
elsif @options[:at] || @options[:in]
|
51
|
+
@repeats = false
|
52
|
+
if @options[:at]
|
53
|
+
starting = parse_time(@options[:at])
|
54
|
+
else
|
55
|
+
starting = Time.now + parse_time(@options[:in])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if starting == :now
|
60
|
+
run_and_reschedule(increment)
|
61
|
+
return self
|
62
|
+
else
|
63
|
+
starting = parse_time(starting)
|
64
|
+
end
|
65
|
+
|
66
|
+
time = calculate_delta_time(starting)
|
67
|
+
|
68
|
+
@kickoff_timer = EM.add_timer(time) {
|
69
|
+
@kickoff_timer = nil
|
70
|
+
run_and_reschedule(increment)
|
71
|
+
}
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def calculate_delta_time(the_time)
|
78
|
+
increment = @options[:every] || 1
|
79
|
+
if the_time.is_a?(Time)
|
80
|
+
time = the_time - Time.now
|
81
|
+
else
|
82
|
+
time = the_time
|
83
|
+
end
|
84
|
+
while time < 0
|
85
|
+
time += increment
|
86
|
+
end
|
87
|
+
time
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_time(some_time)
|
91
|
+
if some_time.is_a?(String)
|
92
|
+
Object.const_defined?("Chronic") ?
|
93
|
+
Chronic.parse(some_time) :
|
94
|
+
Time.parse(some_time)
|
95
|
+
elsif some_time.is_a?(Time) || some_time.kind_of?(Numeric)
|
96
|
+
some_time
|
97
|
+
else
|
98
|
+
Time.now
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def run_and_reschedule_cron
|
103
|
+
starting =
|
104
|
+
EventMachine::Timers::CronLine.new(options[:cron]).next_time
|
105
|
+
@reschedule_timer = EM.add_timer(calculate_delta_time(starting)) {
|
106
|
+
run_and_reschedule_cron
|
107
|
+
}
|
108
|
+
@block.call
|
109
|
+
end
|
110
|
+
|
111
|
+
def run_and_reschedule(inc)
|
112
|
+
if @repeats
|
113
|
+
@reschedule_timer = EM.add_timer(inc) {
|
114
|
+
run_and_reschedule(inc)
|
115
|
+
}
|
116
|
+
end
|
117
|
+
@block.call
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.do(options={}, &blk)
|
123
|
+
Timer.new(options, blk).schedule
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.do_hourly(options={}, &blk)
|
127
|
+
self.do({:every => 1.hour}.merge(options), &blk)
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.do_daily(options={}, &blk)
|
131
|
+
self.do({:every => 1.day}.merge(options), &blk)
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.do_weekly(options={}, &blk)
|
135
|
+
self.do({:every => 1.week}.merge(options), &blk)
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.do_monthly(options={}, &blk)
|
139
|
+
self.do({:every => 1.month}.merge(options), &blk)
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.cron(spec, &blk)
|
143
|
+
self.do({:cron => spec}, &blk)
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.list
|
147
|
+
Timer.list
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.find_by_tag(tag)
|
151
|
+
Timer.list.find_all { |t|
|
152
|
+
t.tag == tag
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.find_by_name(name)
|
157
|
+
Timer.list.find_all { |t|
|
158
|
+
t.name == name
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Stolen directly from ramaze/snippets
|
2
|
+
module NumericMixable
|
3
|
+
def seconds
|
4
|
+
self
|
5
|
+
end
|
6
|
+
alias second seconds
|
7
|
+
|
8
|
+
# 60 seconds in a minute
|
9
|
+
def minutes
|
10
|
+
self * 60
|
11
|
+
end
|
12
|
+
alias minute minutes
|
13
|
+
|
14
|
+
# 60 minutes in an hour
|
15
|
+
def hours
|
16
|
+
self * 3600
|
17
|
+
end
|
18
|
+
alias hour hours
|
19
|
+
|
20
|
+
# 24 hours in a day
|
21
|
+
def days
|
22
|
+
self * 86400
|
23
|
+
end
|
24
|
+
alias day days
|
25
|
+
|
26
|
+
# 7 days in a week
|
27
|
+
def weeks
|
28
|
+
self * 604800
|
29
|
+
end
|
30
|
+
alias week weeks
|
31
|
+
|
32
|
+
# 30 days in a month
|
33
|
+
def months
|
34
|
+
self * 2592000
|
35
|
+
end
|
36
|
+
alias month months
|
37
|
+
|
38
|
+
# 365.25 days in a year
|
39
|
+
def years
|
40
|
+
self * 883612800
|
41
|
+
end
|
42
|
+
alias year years
|
43
|
+
|
44
|
+
# Time in the past, i.e. 3.days.ago
|
45
|
+
def ago t = Time.now
|
46
|
+
t - self
|
47
|
+
end
|
48
|
+
alias before ago
|
49
|
+
|
50
|
+
# Time in the future, i.e. 3.days.from_now
|
51
|
+
def from_now t = Time.now
|
52
|
+
t + self
|
53
|
+
end
|
54
|
+
alias since from_now
|
55
|
+
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-timers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jake Douglas
|
8
|
+
- Colin Steele
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2009-11-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - '>='
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
description: helper methods for timers in EventMachine
|
29
|
+
email: jakecdouglas@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- README
|
35
|
+
- lib/em-timers.rb
|
36
|
+
- lib/em-timers/numericmixable.rb
|
37
|
+
- lib/em-timers/cron_line.rb
|
38
|
+
- lib/em-timers/em-timers.rb
|
39
|
+
homepage: http://www.github.com/yakischloba/em-timers
|
40
|
+
licenses: []
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 2.0.5
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: helper methods for timers in EventMachine
|
62
|
+
test_files: []
|