clockwork 0.3.2 → 0.3.3
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/Rakefile +11 -11
- data/VERSION +1 -1
- data/bin/clockwork +2 -2
- data/lib/clockwork.rb +168 -136
- data/test/clockwork_test.rb +161 -135
- metadata +5 -2
data/Rakefile
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
require 'jeweler'
|
2
2
|
|
3
3
|
Jeweler::Tasks.new do |s|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
s.name = "clockwork"
|
5
|
+
s.summary = "A scheduler process to replace cron."
|
6
|
+
s.description = "A scheduler process to replace cron, using a more flexible Ruby syntax running as a single long-running process. Inspired by rufus-scheduler and resque-scheduler."
|
7
|
+
s.author = "Adam Wiggins"
|
8
|
+
s.email = "adam@heroku.com"
|
9
|
+
s.homepage = "http://github.com/adamwiggins/clockwork"
|
10
|
+
s.executables = [ "clockwork" ]
|
11
|
+
s.rubyforge_project = "clockwork"
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
s.files = FileList["[A-Z]*", "{bin,lib}/**/*"]
|
14
|
+
s.test_files = FileList["{test}/**/*"]
|
15
15
|
end
|
16
16
|
|
17
17
|
Jeweler::GemcutterTasks.new
|
18
18
|
|
19
19
|
task 'test' do
|
20
|
-
|
20
|
+
sh "ruby test/clockwork_test.rb"
|
21
21
|
end
|
22
22
|
|
23
23
|
task :build => :test
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.3
|
data/bin/clockwork
CHANGED
data/lib/clockwork.rb
CHANGED
@@ -1,145 +1,177 @@
|
|
1
1
|
module Clockwork
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
2
|
+
|
3
|
+
@@events = []
|
4
|
+
|
5
|
+
class At
|
6
|
+
class FailedToParse < StandardError; end;
|
7
|
+
NOT_SPECIFIED = class << nil
|
8
|
+
include Comparable
|
9
|
+
def <=>(other); 0; end # equals to anything
|
10
|
+
end
|
11
|
+
WDAYS = %w[sunday monday tuesday wednesday thursday friday saturday].map do |w|
|
12
|
+
[w, w.capitalize, w[0...3], w[0...3].capitalize]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse(at)
|
16
|
+
return unless at
|
17
|
+
case at
|
18
|
+
when /^([[:alpha:]]+)\s(.*)$/
|
19
|
+
ret = parse($2)
|
20
|
+
wday = WDAYS.find_index {|x| x.include?($1) }
|
21
|
+
raise FailedToParse, at if wday.nil?
|
22
|
+
ret.wday = wday
|
23
|
+
ret
|
24
|
+
when /^(\d{1,2}):(\d\d)$/
|
25
|
+
new($2.to_i, $1.to_i)
|
26
|
+
when /^\*{1,2}:(\d\d)$/
|
27
|
+
new($1.to_i)
|
28
|
+
else
|
29
|
+
raise FailedToParse, at
|
30
|
+
end
|
31
|
+
rescue ArgumentError
|
32
|
+
raise FailedToParse, at
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_writer :min, :hour, :wday
|
36
|
+
|
37
|
+
def initialize(min, hour=NOT_SPECIFIED, wday=NOT_SPECIFIED)
|
38
|
+
if min.nil? || min < 0 || min > 59 ||
|
39
|
+
hour < 0 || hour > 23 ||
|
40
|
+
wday < 0 || wday > 6
|
41
|
+
raise ArgumentError
|
42
|
+
end
|
43
|
+
@min = min
|
44
|
+
@hour = hour
|
45
|
+
@wday = wday
|
46
|
+
end
|
47
|
+
|
48
|
+
def ready?(t)
|
49
|
+
t.min == @min and t.hour == @hour and t.wday == @wday
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Event
|
54
|
+
attr_accessor :job, :last
|
55
|
+
|
56
|
+
def initialize(period, job, block, options={})
|
57
|
+
@period = period
|
58
|
+
@job = job
|
59
|
+
@at = At.parse(options[:at])
|
60
|
+
@last = nil
|
61
|
+
@block = block
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
@job
|
66
|
+
end
|
67
|
+
|
68
|
+
def time?(t)
|
69
|
+
ellapsed_ready = (@last.nil? or (t - @last).to_i >= @period)
|
70
|
+
ellapsed_ready and (@at.nil? or @at.ready?(t))
|
71
|
+
end
|
72
|
+
|
73
|
+
def run(t)
|
74
|
+
@last = t
|
75
|
+
@block.call(@job)
|
76
|
+
rescue => e
|
77
|
+
log_error(e)
|
78
|
+
end
|
79
|
+
|
80
|
+
def log_error(e)
|
81
|
+
STDERR.puts exception_message(e)
|
82
|
+
end
|
83
|
+
|
84
|
+
def exception_message(e)
|
85
|
+
msg = [ "Exception #{e.class} -> #{e.message}" ]
|
86
|
+
|
87
|
+
base = File.expand_path(Dir.pwd) + '/'
|
88
|
+
e.backtrace.each do |t|
|
89
|
+
msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
|
90
|
+
end
|
91
|
+
|
92
|
+
msg.join("\n")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
extend self
|
97
|
+
|
98
|
+
def handler(&block)
|
99
|
+
@@handler = block
|
100
|
+
end
|
101
|
+
|
102
|
+
class NoHandlerDefined < RuntimeError; end
|
103
|
+
|
104
|
+
def get_handler
|
105
|
+
raise NoHandlerDefined unless (defined?(@@handler) and @@handler)
|
106
|
+
@@handler
|
107
|
+
end
|
108
|
+
|
109
|
+
def every(period, job, options={}, &block)
|
110
|
+
if options[:at].respond_to?(:each)
|
111
|
+
each_options = options.clone
|
112
|
+
options[:at].each do |at|
|
113
|
+
each_options[:at] = at
|
114
|
+
register(period, job, block, each_options)
|
115
|
+
end
|
116
|
+
else
|
117
|
+
register(period, job, block, options)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def run
|
122
|
+
log "Starting clock for #{@@events.size} events: [ " + @@events.map { |e| e.to_s }.join(' ') + " ]"
|
123
|
+
loop do
|
124
|
+
tick
|
125
|
+
sleep 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def log(msg)
|
130
|
+
puts msg
|
131
|
+
end
|
132
|
+
|
133
|
+
def tick(t=Time.now)
|
134
|
+
to_run = @@events.select do |event|
|
135
|
+
event.time?(t)
|
136
|
+
end
|
137
|
+
|
138
|
+
to_run.each do |event|
|
139
|
+
log "Triggering #{event}"
|
140
|
+
event.run(t)
|
141
|
+
end
|
142
|
+
|
143
|
+
to_run
|
144
|
+
end
|
145
|
+
|
146
|
+
def clear!
|
147
|
+
@@events = []
|
148
|
+
@@handler = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
def register(period, job, block, options)
|
153
|
+
event = Event.new(period, job, block || get_handler, options)
|
154
|
+
@@events << event
|
155
|
+
event
|
156
|
+
end
|
157
|
+
|
129
158
|
end
|
130
159
|
|
131
|
-
unless 1.respond_to?(:seconds)
|
160
|
+
unless 1.respond_to?(:seconds)
|
132
161
|
class Numeric
|
133
|
-
|
134
|
-
|
162
|
+
def seconds; self; end
|
163
|
+
alias :second :seconds
|
164
|
+
|
165
|
+
def minutes; self * 60; end
|
166
|
+
alias :minute :minutes
|
135
167
|
|
136
|
-
|
137
|
-
|
168
|
+
def hours; self * 3600; end
|
169
|
+
alias :hour :hours
|
138
170
|
|
139
|
-
|
140
|
-
|
171
|
+
def days; self * 86400; end
|
172
|
+
alias :day :days
|
141
173
|
|
142
|
-
|
143
|
-
|
174
|
+
def weeks; self * 604800; end
|
175
|
+
alias :week :weeks
|
144
176
|
end
|
145
177
|
end
|
data/test/clockwork_test.rb
CHANGED
@@ -5,142 +5,168 @@ require 'mocha'
|
|
5
5
|
require 'time'
|
6
6
|
|
7
7
|
module Clockwork
|
8
|
-
|
9
|
-
|
8
|
+
def log(msg)
|
9
|
+
end
|
10
10
|
end
|
11
11
|
|
12
12
|
class ClockworkTest < Test::Unit::TestCase
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
13
|
+
setup do
|
14
|
+
Clockwork.clear!
|
15
|
+
Clockwork.handler { }
|
16
|
+
end
|
17
|
+
|
18
|
+
def assert_will_run(t)
|
19
|
+
if t.is_a? String
|
20
|
+
t = Time.parse(t)
|
21
|
+
end
|
22
|
+
assert_equal 1, Clockwork.tick(t).size
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_wont_run(t)
|
26
|
+
if t.is_a? String
|
27
|
+
t = Time.parse(t)
|
28
|
+
end
|
29
|
+
assert_equal 0, Clockwork.tick(t).size
|
30
|
+
end
|
31
|
+
|
32
|
+
test "once a minute" do
|
33
|
+
Clockwork.every(1.minute, 'myjob')
|
34
|
+
|
35
|
+
assert_will_run(t=Time.now)
|
36
|
+
assert_wont_run(t+30)
|
37
|
+
assert_will_run(t+60)
|
38
|
+
end
|
39
|
+
|
40
|
+
test "every three minutes" do
|
41
|
+
Clockwork.every(3.minutes, 'myjob')
|
42
|
+
|
43
|
+
assert_will_run(t=Time.now)
|
44
|
+
assert_wont_run(t+2*60)
|
45
|
+
assert_will_run(t+3*60)
|
46
|
+
end
|
47
|
+
|
48
|
+
test "once an hour" do
|
49
|
+
Clockwork.every(1.hour, 'myjob')
|
50
|
+
|
51
|
+
assert_will_run(t=Time.now)
|
52
|
+
assert_wont_run(t+30*60)
|
53
|
+
assert_will_run(t+60*60)
|
54
|
+
end
|
55
|
+
|
56
|
+
test "once a week" do
|
57
|
+
Clockwork.every(1.week, 'myjob')
|
58
|
+
|
59
|
+
assert_will_run(t=Time.now)
|
60
|
+
assert_wont_run(t+60*60*24*6)
|
61
|
+
assert_will_run(t+60*60*24*7)
|
62
|
+
end
|
63
|
+
|
64
|
+
test "once a day at 16:20" do
|
65
|
+
Clockwork.every(1.day, 'myjob', :at => '16:20')
|
66
|
+
|
67
|
+
assert_wont_run 'jan 1 2010 16:19:59'
|
68
|
+
assert_will_run 'jan 1 2010 16:20:00'
|
69
|
+
assert_wont_run 'jan 1 2010 16:20:01'
|
70
|
+
assert_wont_run 'jan 2 2010 16:19:59'
|
71
|
+
assert_will_run 'jan 2 2010 16:20:00'
|
72
|
+
end
|
73
|
+
|
74
|
+
test ":at also accepts 8:20" do
|
75
|
+
Clockwork.every(1.hour, 'myjob', :at => '8:20')
|
76
|
+
|
77
|
+
assert_wont_run 'jan 1 2010 08:19:59'
|
78
|
+
assert_will_run 'jan 1 2010 08:20:00'
|
79
|
+
assert_wont_run 'jan 1 2010 08:20:01'
|
80
|
+
end
|
81
|
+
|
82
|
+
test "twice a day at 16:20 and 18:10" do
|
83
|
+
Clockwork.every(1.day, 'myjob', :at => ['16:20', '18:10'])
|
84
|
+
|
85
|
+
assert_wont_run 'jan 1 2010 16:19:59'
|
86
|
+
assert_will_run 'jan 1 2010 16:20:00'
|
87
|
+
assert_wont_run 'jan 1 2010 16:20:01'
|
88
|
+
|
89
|
+
assert_wont_run 'jan 1 2010 18:09:59'
|
90
|
+
assert_will_run 'jan 1 2010 18:10:00'
|
91
|
+
assert_wont_run 'jan 1 2010 18:10:01'
|
92
|
+
end
|
93
|
+
|
94
|
+
test "once an hour at **:20" do
|
95
|
+
Clockwork.every(1.hour, 'myjob', :at => '**:20')
|
96
|
+
|
97
|
+
assert_wont_run 'jan 1 2010 15:19:59'
|
98
|
+
assert_will_run 'jan 1 2010 15:20:00'
|
99
|
+
assert_wont_run 'jan 1 2010 15:20:01'
|
100
|
+
assert_wont_run 'jan 2 2010 16:19:59'
|
101
|
+
assert_will_run 'jan 2 2010 16:20:00'
|
102
|
+
end
|
103
|
+
|
104
|
+
test ":at also accepts *:20" do
|
105
|
+
Clockwork.every(1.hour, 'myjob', :at => '*:20')
|
106
|
+
|
107
|
+
assert_wont_run 'jan 1 2010 15:19:59'
|
108
|
+
assert_will_run 'jan 1 2010 15:20:00'
|
109
|
+
assert_wont_run 'jan 1 2010 15:20:01'
|
110
|
+
end
|
111
|
+
|
112
|
+
test "on every Saturday" do
|
113
|
+
Clockwork.every(1.week, 'myjob', :at => 'Saturday 12:00')
|
114
|
+
|
115
|
+
assert_wont_run 'jan 1 2010 12:00:00'
|
116
|
+
assert_will_run 'jan 2 2010 12:00:00' # Saturday
|
117
|
+
assert_wont_run 'jan 3 2010 12:00:00'
|
118
|
+
assert_wont_run 'jan 8 2010 12:00:00'
|
119
|
+
assert_will_run 'jan 9 2010 12:00:00'
|
120
|
+
end
|
121
|
+
|
122
|
+
test ":at accepts abbreviated weekday" do
|
123
|
+
Clockwork.every(1.week, 'myjob', :at => 'sat 12:00')
|
124
|
+
|
125
|
+
assert_wont_run 'jan 1 2010 12:00:00'
|
126
|
+
assert_will_run 'jan 2 2010 12:00:00' # Saturday
|
127
|
+
assert_wont_run 'jan 3 2010 12:00:00'
|
128
|
+
end
|
129
|
+
|
130
|
+
test "aborts when no handler defined" do
|
131
|
+
Clockwork.clear!
|
132
|
+
assert_raise(Clockwork::NoHandlerDefined) do
|
133
|
+
Clockwork.every(1.minute, 'myjob')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
test "aborts when fails to parse" do
|
138
|
+
assert_raise(Clockwork::At::FailedToParse) do
|
139
|
+
Clockwork.every(1.day, "myjob", :at => "a:bc")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
test "general handler" do
|
144
|
+
$set_me = 0
|
145
|
+
Clockwork.handler { $set_me = 1 }
|
146
|
+
Clockwork.every(1.minute, 'myjob')
|
147
|
+
Clockwork.tick(Time.now)
|
148
|
+
assert_equal 1, $set_me
|
149
|
+
end
|
150
|
+
|
151
|
+
test "event-specific handler" do
|
152
|
+
$set_me = 0
|
153
|
+
Clockwork.every(1.minute, 'myjob') { $set_me = 2 }
|
154
|
+
Clockwork.tick(Time.now)
|
155
|
+
assert_equal 2, $set_me
|
156
|
+
end
|
157
|
+
|
158
|
+
test "exceptions are trapped and logged" do
|
159
|
+
Clockwork.handler { raise 'boom' }
|
160
|
+
event = Clockwork.every(1.minute, 'myjob')
|
161
|
+
event.expects(:log_error)
|
162
|
+
assert_nothing_raised { Clockwork.tick(Time.now) }
|
163
|
+
end
|
164
|
+
|
165
|
+
test "exceptions still set the last timestamp to avoid spastic error loops" do
|
166
|
+
Clockwork.handler { raise 'boom' }
|
167
|
+
event = Clockwork.every(1.minute, 'myjob')
|
168
|
+
event.stubs(:log_error)
|
169
|
+
Clockwork.tick(t = Time.now)
|
170
|
+
assert_equal t, event.last
|
171
|
+
end
|
146
172
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: clockwork
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.3.
|
5
|
+
version: 0.3.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Adam Wiggins
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-12-13 00:00:00 +09:00
|
14
14
|
default_executable: clockwork
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -77,6 +77,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
77
|
requirements:
|
78
78
|
- - ">="
|
79
79
|
- !ruby/object:Gem::Version
|
80
|
+
hash: -31480467
|
81
|
+
segments:
|
82
|
+
- 0
|
80
83
|
version: "0"
|
81
84
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
85
|
none: false
|