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.
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