em-timers 0.2.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.
- 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: []
|