clockwork 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/Rakefile +11 -11
  2. data/VERSION +1 -1
  3. data/bin/clockwork +2 -2
  4. data/lib/clockwork.rb +168 -136
  5. data/test/clockwork_test.rb +161 -135
  6. metadata +5 -2
data/Rakefile CHANGED
@@ -1,23 +1,23 @@
1
1
  require 'jeweler'
2
2
 
3
3
  Jeweler::Tasks.new do |s|
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"
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
- s.files = FileList["[A-Z]*", "{bin,lib}/**/*"]
14
- s.test_files = FileList["{test}/**/*"]
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
- sh "ruby test/clockwork_test.rb"
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.2
1
+ 0.3.3
data/bin/clockwork CHANGED
@@ -12,8 +12,8 @@ file = "./#{file}" unless file.match(/^[\/.]/)
12
12
  require file
13
13
 
14
14
  trap('INT') do
15
- puts "\rExiting"
16
- exit
15
+ puts "\rExiting"
16
+ exit
17
17
  end
18
18
 
19
19
  Clockwork::run
data/lib/clockwork.rb CHANGED
@@ -1,145 +1,177 @@
1
1
  module Clockwork
2
- class FailedToParse < StandardError; end;
3
-
4
- @@events = []
5
-
6
- class Event
7
- attr_accessor :job, :last
8
-
9
- def initialize(period, job, block, options={})
10
- @period = period
11
- @job = job
12
- @at = parse_at(options[:at])
13
- @last = nil
14
- @block = block
15
- end
16
-
17
- def to_s
18
- @job
19
- end
20
-
21
- def time?(t)
22
- ellapsed_ready = (@last.nil? or (t - @last).to_i >= @period)
23
- time_ready = (@at.nil? or ((@at[0].nil? or t.hour == @at[0]) and t.min == @at[1]))
24
- ellapsed_ready and time_ready
25
- end
26
-
27
- def run(t)
28
- @last = t
29
- @block.call(@job)
30
- rescue => e
31
- log_error(e)
32
- end
33
-
34
- def log_error(e)
35
- STDERR.puts exception_message(e)
36
- end
37
-
38
- def exception_message(e)
39
- msg = [ "Exception #{e.class} -> #{e.message}" ]
40
-
41
- base = File.expand_path(Dir.pwd) + '/'
42
- e.backtrace.each do |t|
43
- msg << " #{File.expand_path(t).gsub(/#{base}/, '')}"
44
- end
45
-
46
- msg.join("\n")
47
- end
48
-
49
- def parse_at(at)
50
- return unless at
51
- case at
52
- when /^(\d{1,2}):(\d\d)$/
53
- hour = $1.to_i
54
- min = $2.to_i
55
- raise FailedToParse, at if hour >= 24 || min >= 60
56
- [hour, min]
57
- when /^\*{1,2}:(\d\d)$/
58
- min = $1.to_i
59
- raise FailedToParse, at if min >= 60
60
- [nil, min]
61
- else
62
- raise FailedToParse, at
63
- end
64
- end
65
- end
66
-
67
- extend self
68
-
69
- def handler(&block)
70
- @@handler = block
71
- end
72
-
73
- class NoHandlerDefined < RuntimeError; end
74
-
75
- def get_handler
76
- raise NoHandlerDefined unless (defined?(@@handler) and @@handler)
77
- @@handler
78
- end
79
-
80
- def every(period, job, options={}, &block)
81
- if options[:at].respond_to?(:each)
82
- each_options = options.clone
83
- options[:at].each do |at|
84
- each_options[:at] = at
85
- register(period, job, block, each_options)
86
- end
87
- else
88
- register(period, job, block, options)
89
- end
90
- end
91
-
92
- def run
93
- log "Starting clock for #{@@events.size} events: [ " + @@events.map { |e| e.to_s }.join(' ') + " ]"
94
- loop do
95
- tick
96
- sleep 1
97
- end
98
- end
99
-
100
- def log(msg)
101
- puts msg
102
- end
103
-
104
- def tick(t=Time.now)
105
- to_run = @@events.select do |event|
106
- event.time?(t)
107
- end
108
-
109
- to_run.each do |event|
110
- log "Triggering #{event}"
111
- event.run(t)
112
- end
113
-
114
- to_run
115
- end
116
-
117
- def clear!
118
- @@events = []
119
- @@handler = nil
120
- end
121
-
122
- private
123
- def register(period, job, block, options)
124
- event = Event.new(period, job, block || get_handler, options)
125
- @@events << event
126
- event
127
- end
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
- def seconds; self; end
134
- alias :second :seconds
162
+ def seconds; self; end
163
+ alias :second :seconds
164
+
165
+ def minutes; self * 60; end
166
+ alias :minute :minutes
135
167
 
136
- def minutes; self * 60; end
137
- alias :minute :minutes
168
+ def hours; self * 3600; end
169
+ alias :hour :hours
138
170
 
139
- def hours; self * 3600; end
140
- alias :hour :hours
171
+ def days; self * 86400; end
172
+ alias :day :days
141
173
 
142
- def days; self * 86400; end
143
- alias :day :days
174
+ def weeks; self * 604800; end
175
+ alias :week :weeks
144
176
  end
145
177
  end
@@ -5,142 +5,168 @@ require 'mocha'
5
5
  require 'time'
6
6
 
7
7
  module Clockwork
8
- def log(msg)
9
- end
8
+ def log(msg)
9
+ end
10
10
  end
11
11
 
12
12
  class ClockworkTest < Test::Unit::TestCase
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 day at 16:20" do
57
- Clockwork.every(1.day, 'myjob', :at => '16:20')
58
-
59
- assert_wont_run 'jan 1 2010 16:19:59'
60
- assert_will_run 'jan 1 2010 16:20:00'
61
- assert_wont_run 'jan 1 2010 16:20:01'
62
- assert_wont_run 'jan 2 2010 16:19:59'
63
- assert_will_run 'jan 2 2010 16:20:00'
64
- end
65
-
66
- test ":at also accepts 8:20" do
67
- Clockwork.every(1.hour, 'myjob', :at => '8:20')
68
-
69
- assert_wont_run 'jan 1 2010 08:19:59'
70
- assert_will_run 'jan 1 2010 08:20:00'
71
- assert_wont_run 'jan 1 2010 08:20:01'
72
- end
73
-
74
- test "twice a day at 16:20 and 18:10" do
75
- Clockwork.every(1.day, 'myjob', :at => ['16:20', '18:10'])
76
-
77
- assert_wont_run 'jan 1 2010 16:19:59'
78
- assert_will_run 'jan 1 2010 16:20:00'
79
- assert_wont_run 'jan 1 2010 16:20:01'
80
-
81
- assert_wont_run 'jan 1 2010 18:09:59'
82
- assert_will_run 'jan 1 2010 18:10:00'
83
- assert_wont_run 'jan 1 2010 18:10:01'
84
- end
85
-
86
- test "once an hour at **:20" do
87
- Clockwork.every(1.hour, 'myjob', :at => '**:20')
88
-
89
- assert_wont_run 'jan 1 2010 15:19:59'
90
- assert_will_run 'jan 1 2010 15:20:00'
91
- assert_wont_run 'jan 1 2010 15:20:01'
92
- assert_wont_run 'jan 2 2010 16:19:59'
93
- assert_will_run 'jan 2 2010 16:20:00'
94
- end
95
-
96
- test ":at also accepts *:20" do
97
- Clockwork.every(1.hour, 'myjob', :at => '*:20')
98
-
99
- assert_wont_run 'jan 1 2010 15:19:59'
100
- assert_will_run 'jan 1 2010 15:20:00'
101
- assert_wont_run 'jan 1 2010 15:20:01'
102
- end
103
-
104
- test "aborts when no handler defined" do
105
- Clockwork.clear!
106
- assert_raise(Clockwork::NoHandlerDefined) do
107
- Clockwork.every(1.minute, 'myjob')
108
- end
109
- end
110
-
111
- test "aborts when fails to parse" do
112
- assert_raise(Clockwork::FailedToParse) do
113
- Clockwork.every(1.day, "myjob", :at => "a:bc")
114
- end
115
- end
116
-
117
- test "general handler" do
118
- $set_me = 0
119
- Clockwork.handler { $set_me = 1 }
120
- Clockwork.every(1.minute, 'myjob')
121
- Clockwork.tick(Time.now)
122
- assert_equal 1, $set_me
123
- end
124
-
125
- test "event-specific handler" do
126
- $set_me = 0
127
- Clockwork.every(1.minute, 'myjob') { $set_me = 2 }
128
- Clockwork.tick(Time.now)
129
- assert_equal 2, $set_me
130
- end
131
-
132
- test "exceptions are trapped and logged" do
133
- Clockwork.handler { raise 'boom' }
134
- event = Clockwork.every(1.minute, 'myjob')
135
- event.expects(:log_error)
136
- assert_nothing_raised { Clockwork.tick(Time.now) }
137
- end
138
-
139
- test "exceptions still set the last timestamp to avoid spastic error loops" do
140
- Clockwork.handler { raise 'boom' }
141
- event = Clockwork.every(1.minute, 'myjob')
142
- event.stubs(:log_error)
143
- Clockwork.tick(t = Time.now)
144
- assert_equal t, event.last
145
- end
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.2
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-11-11 00:00:00 +09:00
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