periodic-scheduler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", ">= 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.2"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.6.2)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.9.2)
11
+ rcov (0.9.9)
12
+ rspec (2.6.0)
13
+ rspec-core (~> 2.6.0)
14
+ rspec-expectations (~> 2.6.0)
15
+ rspec-mocks (~> 2.6.0)
16
+ rspec-core (2.6.4)
17
+ rspec-expectations (2.6.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.6.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.0.0)
26
+ jeweler (~> 1.6.2)
27
+ rcov
28
+ rspec (>= 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jakub Pastuszek
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = periodic-scheduler
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to periodic-scheduler
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Jakub Pastuszek. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "periodic-scheduler"
18
+ gem.homepage = "http://github.com/jpastuszek/periodic-scheduler"
19
+ gem.license = "MIT"
20
+ gem.summary = "Scheduler for periodic tasks."
21
+ gem.description = "Controls execution of periodic scheduled tasks."
22
+ gem.email = "jpastuszek@gmail.com"
23
+ gem.authors = ["Jakub Pastuszek"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "periodic-scheduler #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,178 @@
1
+ require 'quantized_time_space'
2
+ require 'set'
3
+
4
+ class PeriodicScheduler
5
+ class MissedScheduleError < RuntimeError; end
6
+
7
+ class Event
8
+ attr_reader :period
9
+ attr_reader :reschedule
10
+ attr_reader :group
11
+ attr_reader :callback
12
+
13
+ def initialize(period, reschedule, group, callback)
14
+ @period = period
15
+ @reschedule = reschedule
16
+ @group = group
17
+ @callback = callback
18
+ end
19
+
20
+ def call
21
+ @callback.call
22
+ end
23
+ end
24
+
25
+ class QuantizedEventBuilder
26
+ class QuantizedEvent < Event
27
+ attr_reader :quantum_period
28
+ attr_reader :quantum_error
29
+
30
+ def initialize(period, reschedule, group, callback, quantum_period, quantum_error)
31
+ super(period, reschedule, group, callback)
32
+ @quantum_period = quantum_period
33
+ @quantum_error = quantum_error
34
+ end
35
+ end
36
+
37
+ def initialize(quantized_space)
38
+ @quantized_space = quantized_space
39
+ end
40
+
41
+ def from_event(event)
42
+ QuantizedEvent.new(
43
+ event.period,
44
+ event.reschedule,
45
+ event.group,
46
+ event.callback,
47
+ @quantized_space.project(event.period),
48
+ @quantized_space.projection_error(event.period)
49
+ )
50
+ end
51
+
52
+ def reschedule(quantized_event)
53
+ accumulated_period = quantized_event.period + quantized_event.quantum_error
54
+ QuantizedEvent.new(
55
+ quantized_event.period,
56
+ quantized_event.reschedule,
57
+ quantized_event.group,
58
+ quantized_event.callback,
59
+ @quantized_space.project(accumulated_period),
60
+ @quantized_space.projection_error(accumulated_period)
61
+ )
62
+ end
63
+ end
64
+
65
+
66
+ def initialize(quantum = 5.0, time_source = lambda {Time.now.to_f}, wait_function = lambda{|t| sleep t})
67
+ @quantized_space = RealTimeToQuantizedSpaceProjection.new(
68
+ quantum,
69
+ lambda {|v| v.floor}
70
+ )
71
+ @quantized_event_builder = QuantizedEventBuilder.new(@quantized_space)
72
+ @time_source = time_source
73
+ @wait_function = wait_function
74
+
75
+ @events = {}
76
+ @event_groups_to_unschedule = Set.new
77
+ end
78
+
79
+ def schedule(period, reschedule = false, group = nil, &callback)
80
+ event = @quantized_event_builder.from_event(Event.new(period, reschedule, group, callback))
81
+ period = quantized_now + event.quantum_period
82
+ add_event(event, period)
83
+ end
84
+
85
+ def unschedule_group(group)
86
+ @event_groups_to_unschedule << group
87
+ end
88
+
89
+ def wait_events
90
+ process_unsheduled_events
91
+
92
+ earliest_quant = @events.keys.sort[0]
93
+ # no more events left
94
+ return nil unless earliest_quant
95
+
96
+ errors = []
97
+
98
+ wait_time = @quantized_space.revers_project(earliest_quant) - real_now
99
+ if wait_time < 0
100
+ # we have missed our scheduled period
101
+ begin
102
+ # we raise it so it has proper content (backtrace)
103
+ raise MissedScheduleError.new("missed schedule by #{-wait_time} seconds")
104
+ rescue StandardError => ex
105
+ errors << ex
106
+ end
107
+
108
+ wait_time = 0
109
+ end
110
+ wait(wait_time)
111
+
112
+ qnow = quantized_now
113
+ quants = @events.keys.select{|k| k <= qnow}.sort
114
+ # It may happen that wait returned qucker that it should
115
+ if quants.empty?
116
+ return wait_events
117
+ end
118
+
119
+ quants.each do |q|
120
+ events = @events[q]
121
+ @events.delete(q)
122
+ events.each do |e|
123
+ begin
124
+ e.call
125
+ rescue StandardError => ex
126
+ errors << ex
127
+ end
128
+ reschedule_event(e, q) if e.reschedule
129
+ end
130
+ end
131
+
132
+ errors
133
+ end
134
+
135
+ private
136
+
137
+ def process_unsheduled_events
138
+ return if @event_groups_to_unschedule.empty?
139
+
140
+ new_events = {}
141
+
142
+ @events.each_pair do |quant, events|
143
+ evs = events.select do |event|
144
+ not @event_groups_to_unschedule.member?(event.group)
145
+ end
146
+
147
+ new_events[quant] = evs unless evs.empty?
148
+ end
149
+
150
+ @events = new_events
151
+ @event_groups_to_unschedule = Set.new
152
+ end
153
+
154
+ def add_event(quantized_event, period)
155
+ @events[period] = [] unless @events[period]
156
+ @events[period] << quantized_event
157
+ end
158
+
159
+ def reschedule_event(quantized_event, previous_run_quant)
160
+ event = @quantized_event_builder.reschedule(quantized_event)
161
+ period = previous_run_quant + event.quantum_period
162
+ add_event(event, period)
163
+ end
164
+
165
+ def wait(time)
166
+ fail "time must be a positive number" if time < 0
167
+ @wait_function.call(time)
168
+ end
169
+
170
+ def real_now
171
+ @time_source.call
172
+ end
173
+
174
+ def quantized_now
175
+ @quantized_space.project(real_now)
176
+ end
177
+ end
178
+
@@ -0,0 +1,20 @@
1
+ class RealTimeToQuantizedSpaceProjection
2
+ def initialize(quantum_size, quantization_rule)
3
+ @quantum_size = quantum_size
4
+ @quantization_rule = quantization_rule
5
+ end
6
+
7
+ def project(value)
8
+ @quantization_rule.call(value / @quantum_size)
9
+ end
10
+
11
+ def revers_project(value)
12
+ value * @quantum_size
13
+ end
14
+
15
+ def projection_error(value)
16
+ new_value = project(value)
17
+ value - revers_project(new_value)
18
+ end
19
+ end
20
+
@@ -0,0 +1,373 @@
1
+ require 'periodic-scheduler'
2
+
3
+ describe PeriodicScheduler do
4
+ before :each do
5
+ @time_now = 0
6
+ @time = lambda{@time_now}
7
+ @wait = lambda{|t|
8
+ @time_now += t
9
+ #puts "sleeping for #{t}"
10
+ }
11
+
12
+ @got_events = []
13
+ @got_event = lambda{|no|
14
+ #puts "event #{no} at #{@time_now}"
15
+ @got_events << no
16
+ }
17
+ end
18
+
19
+ it "should execut event callbacks given time progress" do
20
+ s = PeriodicScheduler.new(5.0, @time, @wait)
21
+
22
+ s.schedule(11.5) do
23
+ @got_event.call(1)
24
+ end
25
+
26
+ s.schedule(14) do
27
+ @got_event.call(2)
28
+ end
29
+
30
+ s.schedule(20) do
31
+ @got_event.call(3)
32
+ end
33
+
34
+ @got_events.should == []
35
+
36
+ s.wait_events
37
+ @got_events.should == [1, 2]
38
+ @time_now.should == 10.0
39
+
40
+ s.wait_events
41
+ @got_events.should == [1, 2, 3]
42
+ @time_now.should == 20.0
43
+
44
+ # They should not get resheduled by default
45
+ s.wait_events
46
+ @got_events.should == [1, 2, 3]
47
+
48
+ s.wait_events
49
+ @got_events.should == [1, 2, 3]
50
+ end
51
+
52
+ it "should reschedule resheduable tasks" do
53
+ s = PeriodicScheduler.new(5.0, @time, @wait)
54
+
55
+ s.schedule(15, true) do
56
+ @got_event.call(1)
57
+ end
58
+
59
+ @got_events.should == []
60
+
61
+ s.wait_events
62
+ @got_events.should == [1]
63
+ @time_now.should == 15.0
64
+
65
+ s.wait_events
66
+ @got_events.should == [1, 1]
67
+ @time_now.should == 30
68
+
69
+ s.wait_events
70
+ @got_events.should == [1, 1, 1]
71
+ @time_now.should == 45
72
+ end
73
+
74
+ it "should compensate for quntization error" do
75
+ s = PeriodicScheduler.new(5.0, @time, @wait)
76
+
77
+ # Note that now we are using floor to quantize event
78
+ s.schedule(12, true) do
79
+ @got_event.call(1)
80
+ end
81
+
82
+ @got_events.should == []
83
+
84
+ s.wait_events
85
+ @got_events.should == [1]
86
+ @time_now.should == 10
87
+
88
+ s.wait_events
89
+ @got_events.should == [1, 1]
90
+ @time_now.should == 20
91
+
92
+ s.wait_events
93
+ @got_events.should == [1, 1, 1]
94
+ @time_now.should == 35
95
+
96
+ s.wait_events
97
+ @got_events.should == [1, 1, 1, 1]
98
+ @time_now.should == 45
99
+
100
+ s.wait_events
101
+ @got_events.should == [1, 1, 1, 1, 1]
102
+ @time_now.should == 60
103
+
104
+ s.wait_events
105
+ @got_events.should == [1, 1, 1, 1, 1, 1]
106
+ @time_now.should == 70
107
+ end
108
+
109
+ it "should compensate for wait function jitter" do
110
+ jitter = [1, 0, 5, -1, 0.5, -0.2, 0, 0, 0]
111
+ @wait = lambda{|t|
112
+ j = jitter.shift
113
+ #puts "time is: #{@time_now}"
114
+ @time_now += t + j
115
+ #puts "sleeping fou #{t} + jitter #{j}: #{t + j}"
116
+ }
117
+
118
+ s = PeriodicScheduler.new(5.0, @time, @wait)
119
+
120
+ s.schedule(12, true) do
121
+ @got_event.call(1)
122
+ end
123
+
124
+ @got_events.should == []
125
+
126
+ s.wait_events
127
+ @got_events.should == [1]
128
+ @time_now.should == 11
129
+
130
+ s.wait_events
131
+ @got_events.should == [1, 1]
132
+ @time_now.should == 20
133
+
134
+ s.wait_events
135
+ @got_events.should == [1, 1, 1]
136
+ @time_now.should == 40
137
+
138
+ s.wait_events
139
+ @got_events.should == [1, 1, 1, 1]
140
+ @time_now.should == 45.5
141
+
142
+ s.wait_events
143
+ @got_events.should == [1, 1, 1, 1, 1]
144
+ @time_now.should == 60
145
+
146
+ s.wait_events
147
+ @got_events.should == [1, 1, 1, 1, 1, 1]
148
+ @time_now.should == 70.0
149
+
150
+ s.wait_events
151
+ @got_events.should == [1, 1, 1, 1, 1, 1, 1]
152
+ @time_now.should == 80.0
153
+ end
154
+
155
+ it "should keep average scheduling precision over longer time" do
156
+ srand(100) # make rand deterministic
157
+ @wait = lambda{|t|
158
+ j = (rand - 0.5) * 10
159
+ @time_now += t + j
160
+ }
161
+
162
+ ev1_val = Math::PI * 10
163
+ ev1 = []
164
+ ev1_last = 0
165
+
166
+ ev2_val = Math::E * 5
167
+ ev2 = []
168
+ ev2_last = 0
169
+
170
+ s = PeriodicScheduler.new(5.0, @time, @wait)
171
+
172
+ s.schedule(ev1_val, true) do
173
+ ev1 << @time_now - ev1_last
174
+ ev1_last = @time_now
175
+ end
176
+
177
+ s.schedule(ev2_val, true) do
178
+ ev2 << @time_now - ev2_last
179
+ ev2_last = @time_now
180
+ end
181
+
182
+ 10000.times{ s.wait_events }
183
+
184
+ (ev1.inject(0){|v, s| v + s} / ev1.length).should be_within(0.001).of(ev1_val)
185
+ (ev2.inject(0){|v, s| v + s} / ev2.length).should be_within(0.001).of(ev2_val)
186
+ end
187
+
188
+ it "should support grouping of events" do
189
+ s = PeriodicScheduler.new(5.0, @time, @wait)
190
+
191
+ s.schedule(12, true, "test group") {}
192
+ end
193
+
194
+ it "should support unscheduling of all events within a group" do
195
+ s = PeriodicScheduler.new(5.0, @time, @wait)
196
+
197
+ s.schedule(15, true, "g1") do
198
+ @got_event.call(1)
199
+ end
200
+
201
+ s.schedule(20, true, "g1") do
202
+ @got_event.call(2)
203
+ end
204
+
205
+ s.schedule(25, true, "g2") do
206
+ @got_event.call(3)
207
+ end
208
+
209
+ @got_events.should == []
210
+
211
+ s.wait_events
212
+ @got_events.should == [1]
213
+ @time_now.should == 15
214
+
215
+ s.wait_events
216
+ @got_events.should == [1, 2]
217
+ @time_now.should == 20
218
+
219
+ s.wait_events
220
+ @got_events.should == [1, 2, 3]
221
+ @time_now.should == 25
222
+
223
+ s.wait_events
224
+ @got_events.should == [1, 2, 3, 1]
225
+ @time_now.should == 30
226
+
227
+ s.unschedule_group("g1")
228
+
229
+ s.wait_events
230
+ @got_events.should == [1, 2, 3, 1, 3]
231
+ @time_now.should == 50
232
+
233
+ s.unschedule_group("g2")
234
+
235
+ s.wait_events.should be_nil
236
+ @got_events.should == [1, 2, 3, 1, 3]
237
+ @time_now.should == 50
238
+ end
239
+
240
+ it "should support unscheduling of all events within a group from other event" do
241
+ s = PeriodicScheduler.new(1.0, @time, @wait)
242
+
243
+ s.schedule(2, true, "g4") do
244
+ s.unschedule_group("g2")
245
+ @got_event.call(4)
246
+ end
247
+
248
+ s.schedule(1, true, "g1") do
249
+ @got_event.call(1)
250
+ end
251
+
252
+ s.schedule(1, true, "g2") do
253
+ @got_event.call(2)
254
+ end
255
+
256
+ s.schedule(1, true, "g2") do
257
+ @got_event.call(3)
258
+ end
259
+
260
+ @got_events.should == []
261
+
262
+ s.wait_events
263
+ @got_events.should == [1, 2, 3]
264
+ @time_now.should == 1
265
+
266
+ # They will get executed this time
267
+ s.wait_events
268
+ @got_events.should == [1, 2, 3, 4, 1, 2, 3]
269
+ @time_now.should == 2
270
+
271
+ # They should be not rescheduled and gone
272
+ s.wait_events
273
+ @got_events.should == [1, 2, 3, 4, 1, 2, 3, 1]
274
+ @time_now.should == 3
275
+ end
276
+
277
+ it "should execut all not reschedulable tasks if we miss them" do
278
+ s = PeriodicScheduler.new(5.0, @time, @wait)
279
+
280
+ s.schedule(15) do
281
+ @got_event.call(1)
282
+ end
283
+
284
+ s.schedule(30) do
285
+ @got_event.call(2)
286
+ end
287
+
288
+ s.schedule(45) do
289
+ @got_event.call(3)
290
+ end
291
+
292
+ @wait.call(35)
293
+
294
+ s.wait_events.should
295
+ @got_events.should == [1, 2]
296
+
297
+ s.wait_events.should
298
+ @got_events.should == [1, 2, 3]
299
+ end
300
+
301
+ it "should skpi reschedulable tasks if we miss them" do
302
+ #TODO: this behaviour is a bit of gary area
303
+ s = PeriodicScheduler.new(5.0, @time, @wait)
304
+
305
+ s.schedule(15, true) do
306
+ @got_event.call(1)
307
+ end
308
+
309
+ @wait.call(35)
310
+
311
+ s.wait_events.should
312
+ @got_events.should == [1]
313
+
314
+ s.wait_events.should
315
+ @got_events.should == [1, 1]
316
+ end
317
+
318
+ it "should report error if the schedule was missed" do
319
+ s = PeriodicScheduler.new(5.0, @time, @wait)
320
+
321
+ s.schedule(15) do
322
+ @got_event.call(1)
323
+ end
324
+
325
+ s.schedule(30) do
326
+ @got_event.call(2)
327
+ end
328
+
329
+ @wait.call(35)
330
+
331
+ errors = s.wait_events
332
+ errors.should_not be_empty
333
+ errors[0].class.should == PeriodicScheduler::MissedScheduleError
334
+ @got_events.should == [1, 2]
335
+ end
336
+
337
+ end
338
+
339
+ describe PeriodicScheduler, "wait_events" do
340
+ before :each do
341
+ @time_now = 0
342
+ @time = lambda{@time_now}
343
+ @wait = lambda{|t| @time_now += t}
344
+ end
345
+
346
+ it "should handle events call exceptions and return them" do
347
+ s = PeriodicScheduler.new(5.0, @time, @wait)
348
+
349
+ s.schedule(12, true) do
350
+ fail "test"
351
+ end
352
+
353
+ errors = nil
354
+
355
+ lambda {
356
+ errors = s.wait_events
357
+ }.should_not raise_exception
358
+
359
+ errors.should have(1).error
360
+ errors[0].should be_kind_of RuntimeError
361
+ errors[0].to_s.should == "test"
362
+ end
363
+
364
+ it "should return nil if there are no events left to process" do
365
+ s = PeriodicScheduler.new(5.0, @time, @wait)
366
+
367
+ s.schedule(12) {}
368
+
369
+ s.wait_events.should_not be_nil
370
+ s.wait_events.should be_nil
371
+ end
372
+ end
373
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'scheduler'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: periodic-scheduler
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Jakub Pastuszek
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-06-18 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 2
31
+ - 3
32
+ - 0
33
+ version: 2.3.0
34
+ name: rspec
35
+ requirement: *id001
36
+ type: :development
37
+ - !ruby/object:Gem::Dependency
38
+ prerelease: false
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 23
45
+ segments:
46
+ - 1
47
+ - 0
48
+ - 0
49
+ version: 1.0.0
50
+ name: bundler
51
+ requirement: *id002
52
+ type: :development
53
+ - !ruby/object:Gem::Dependency
54
+ prerelease: false
55
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 11
61
+ segments:
62
+ - 1
63
+ - 6
64
+ - 2
65
+ version: 1.6.2
66
+ name: jeweler
67
+ requirement: *id003
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ prerelease: false
71
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ name: rcov
81
+ requirement: *id004
82
+ type: :development
83
+ description: Controls execution of periodic scheduled tasks.
84
+ email: jpastuszek@gmail.com
85
+ executables: []
86
+
87
+ extensions: []
88
+
89
+ extra_rdoc_files:
90
+ - LICENSE.txt
91
+ - README.rdoc
92
+ files:
93
+ - .document
94
+ - .rspec
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE.txt
98
+ - README.rdoc
99
+ - Rakefile
100
+ - VERSION
101
+ - lib/periodic-scheduler.rb
102
+ - lib/quantized_time_space.rb
103
+ - spec/periodic-scheduler_spec.rb
104
+ - spec/spec_helper.rb
105
+ has_rdoc: true
106
+ homepage: http://github.com/jpastuszek/periodic-scheduler
107
+ licenses:
108
+ - MIT
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ requirements: []
133
+
134
+ rubyforge_project:
135
+ rubygems_version: 1.3.7
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: Scheduler for periodic tasks.
139
+ test_files: []
140
+