lowang-whenever 0.7.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,145 @@
1
+ module Whenever
2
+ class JobList
3
+ def initialize(options)
4
+ @jobs, @env, @set_variables, @pre_set_variables = {}, {}, {}, {}
5
+
6
+ if options.is_a? String
7
+ options = { :string => options }
8
+ end
9
+
10
+ pre_set(options[:set])
11
+
12
+ setup = File.read("#{File.expand_path(File.dirname(__FILE__))}/setup.rb")
13
+ schedule = if options[:string]
14
+ options[:string]
15
+ elsif options[:file]
16
+ File.read(options[:file])
17
+ end
18
+
19
+ instance_eval(setup + schedule, options[:file] || '<eval>')
20
+ end
21
+
22
+ def set(variable, value)
23
+ variable = variable.to_sym
24
+ return if @pre_set_variables[variable]
25
+
26
+ instance_variable_set("@#{variable}".to_sym, value)
27
+ self.class.send(:attr_reader, variable.to_sym)
28
+ @set_variables[variable] = value
29
+ end
30
+
31
+ def env(variable, value)
32
+ @env[variable.to_s] = value
33
+ end
34
+
35
+ def every(frequency, options = {})
36
+ @current_time_scope = frequency
37
+ @options = options
38
+ yield
39
+ end
40
+
41
+ def job_type(name, template)
42
+ class_eval do
43
+ define_method(name) do |task, *args|
44
+ options = { :task => task, :template => template }
45
+ options.merge!(args[0]) if args[0].is_a? Hash
46
+
47
+ # :cron_log was an old option for output redirection, it remains for backwards compatibility
48
+ options[:output] = (options[:cron_log] || @cron_log) if defined?(@cron_log) || options.has_key?(:cron_log)
49
+ # :output is the newer, more flexible option.
50
+ options[:output] = @output if defined?(@output) && !options.has_key?(:output)
51
+
52
+ @jobs[@current_time_scope] ||= []
53
+ @jobs[@current_time_scope] << Whenever::Job.new(@options.merge(@set_variables).merge(options))
54
+ end
55
+ end
56
+ end
57
+
58
+ def generate_cron_output
59
+ [environment_variables, cron_jobs].compact.join
60
+ end
61
+
62
+ private
63
+
64
+ #
65
+ # Takes a string like: "variable1=something&variable2=somethingelse"
66
+ # and breaks it into variable/value pairs. Used for setting variables at runtime from the command line.
67
+ # Only works for setting values as strings.
68
+ #
69
+ def pre_set(variable_string = nil)
70
+ return if variable_string.blank?
71
+
72
+ pairs = variable_string.split('&')
73
+ pairs.each do |pair|
74
+ next unless pair.index('=')
75
+ variable, value = *pair.split('=')
76
+ unless variable.blank? || value.blank?
77
+ variable = variable.strip.to_sym
78
+ set(variable, value.strip)
79
+ @pre_set_variables[variable] = value
80
+ end
81
+ end
82
+ end
83
+
84
+ def environment_variables
85
+ return if @env.empty?
86
+
87
+ output = []
88
+ @env.each do |key, val|
89
+ output << "#{key}=#{val.blank? ? '""' : val}\n"
90
+ end
91
+ output << "\n"
92
+
93
+ output.join
94
+ end
95
+
96
+ #
97
+ # Takes the standard cron output that Whenever generates and finds
98
+ # similar entries that can be combined. For example: If a job should run
99
+ # at 3:02am and 4:02am, instead of creating two jobs this method combines
100
+ # them into one that runs on the 2nd minute at the 3rd and 4th hour.
101
+ #
102
+ def combine(entries)
103
+ entries.map! { |entry| entry.split(/ +/, 6) }
104
+ 0.upto(4) do |f|
105
+ (entries.length-1).downto(1) do |i|
106
+ next if entries[i][f] == '*'
107
+ comparison = entries[i][0...f] + entries[i][f+1..-1]
108
+ (i-1).downto(0) do |j|
109
+ next if entries[j][f] == '*'
110
+ if comparison == entries[j][0...f] + entries[j][f+1..-1]
111
+ entries[j][f] += ',' + entries[i][f]
112
+ entries.delete_at(i)
113
+ break
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ entries.map { |entry| entry.join(' ') }
120
+ end
121
+
122
+ def cron_jobs
123
+ return if @jobs.empty?
124
+
125
+ shortcut_jobs = []
126
+ regular_jobs = []
127
+
128
+ @jobs.each do |time, jobs|
129
+ jobs.each do |job|
130
+ Whenever::Output::Cron.output(time, job) do |cron|
131
+ cron << "\n\n"
132
+
133
+ if cron.starts_with?("@")
134
+ shortcut_jobs << cron
135
+ else
136
+ regular_jobs << cron
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ shortcut_jobs.join + combine(regular_jobs).join
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,56 @@
1
+ module Whenever
2
+ module Output
3
+ class Redirection
4
+ def initialize(output)
5
+ @output = output
6
+ end
7
+
8
+ def to_s
9
+ return '' unless defined?(@output)
10
+ case @output
11
+ when String then redirect_from_string
12
+ when Hash then redirect_from_hash
13
+ when NilClass then ">> /dev/null 2>&1"
14
+ else ''
15
+ end
16
+ end
17
+
18
+ protected
19
+
20
+ def stdout
21
+ return unless @output.has_key?(:standard)
22
+ @output[:standard].nil? ? '/dev/null' : @output[:standard]
23
+ end
24
+
25
+ def stderr
26
+ return unless @output.has_key?(:error)
27
+ @output[:error].nil? ? '/dev/null' : @output[:error]
28
+ end
29
+
30
+ def redirect_from_hash
31
+ case
32
+ when stdout == '/dev/null' && stderr == '/dev/null'
33
+ "> /dev/null 2>&1"
34
+ when stdout && stderr == '/dev/null'
35
+ ">> #{stdout} 2> /dev/null"
36
+ when stdout && stderr
37
+ ">> #{stdout} 2>> #{stderr}"
38
+ when stderr == '/dev/null'
39
+ "2> /dev/null"
40
+ when stderr
41
+ "2>> #{stderr}"
42
+ when stdout == '/dev/null'
43
+ "> /dev/null"
44
+ when stdout
45
+ ">> #{stdout}"
46
+ else
47
+ ''
48
+ end
49
+ end
50
+
51
+ def redirect_from_string
52
+ ">> #{@output} 2>&1"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ # Environment defaults to production
2
+ set :environment, "production"
3
+ # Path defaults to the directory `whenever` was run from
4
+ set :path, Whenever.path
5
+
6
+ # All jobs are wrapped in this template.
7
+ # http://blog.scoutapp.com/articles/2010/09/07/rvm-and-cron-in-production
8
+ set :job_template, "/bin/bash -l -c ':job'"
9
+
10
+ job_type :command, ":task :output"
11
+
12
+ # Run rake through bundler if possible
13
+ if Whenever.bundler?
14
+ job_type :rake, "cd :path && RAILS_ENV=:environment bundle exec rake :task --silent :output"
15
+ else
16
+ job_type :rake, "cd :path && RAILS_ENV=:environment rake :task --silent :output"
17
+ end
18
+
19
+ # Create a runner job that's appropriate for the Rails version,
20
+ if Whenever.rails3?
21
+ job_type :runner, "cd :path && script/rails runner -e :environment ':task' :output"
22
+ else
23
+ job_type :runner, "cd :path && script/runner -e :environment ':task' :output"
24
+ end
@@ -0,0 +1,3 @@
1
+ module Whenever
2
+ VERSION = '0.7.0.1'
3
+ end
@@ -0,0 +1,322 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
2
+
3
+ class CommandLineTest < Test::Unit::TestCase
4
+
5
+ context "A command line write" do
6
+ setup do
7
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
8
+ @command = Whenever::CommandLine.new(:write => true, :identifier => 'My identifier')
9
+ @task = "#{two_hours} /my/command"
10
+ Whenever.expects(:cron).returns(@task)
11
+ end
12
+
13
+ should "output the cron job with identifier blocks" do
14
+ output = <<-EXPECTED
15
+ # Begin Whenever generated tasks for: My identifier
16
+ #{@task}
17
+ # End Whenever generated tasks for: My identifier
18
+ EXPECTED
19
+
20
+ assert_equal output, @command.send(:whenever_cron)
21
+ end
22
+
23
+ should "write the crontab when run" do
24
+ @command.expects(:write_crontab).returns(true)
25
+ assert @command.run
26
+ end
27
+ end
28
+
29
+ context "A command line update" do
30
+ setup do
31
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
32
+ @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier')
33
+ @task = "#{two_hours} /my/command"
34
+ Whenever.expects(:cron).returns(@task)
35
+ end
36
+
37
+ should "add the cron to the end of the file if there is no existing identifier block" do
38
+ existing = '# Existing crontab'
39
+ @command.expects(:read_crontab).at_least_once.returns(existing)
40
+
41
+ new_cron = <<-EXPECTED
42
+ #{existing}
43
+
44
+ # Begin Whenever generated tasks for: My identifier
45
+ #{@task}
46
+ # End Whenever generated tasks for: My identifier
47
+ EXPECTED
48
+
49
+ assert_equal new_cron, @command.send(:updated_crontab)
50
+
51
+ @command.expects(:write_crontab).with(new_cron).returns(true)
52
+ assert @command.run
53
+ end
54
+
55
+ should "replace an existing block if the identifier matches" do
56
+ existing = <<-EXISTING_CRON
57
+ # Something
58
+
59
+ # Begin Whenever generated tasks for: My identifier
60
+ My whenever job that was already here
61
+ # End Whenever generated tasks for: My identifier
62
+
63
+ # Begin Whenever generated tasks for: Other identifier
64
+ This shouldn't get replaced
65
+ # End Whenever generated tasks for: Other identifier
66
+ EXISTING_CRON
67
+
68
+ new_cron = <<-NEW_CRON
69
+ # Something
70
+
71
+ # Begin Whenever generated tasks for: My identifier
72
+ #{@task}
73
+ # End Whenever generated tasks for: My identifier
74
+
75
+ # Begin Whenever generated tasks for: Other identifier
76
+ This shouldn't get replaced
77
+ # End Whenever generated tasks for: Other identifier
78
+ NEW_CRON
79
+
80
+ @command.expects(:read_crontab).at_least_once.returns(existing)
81
+ assert_equal new_cron, @command.send(:updated_crontab)
82
+
83
+ @command.expects(:write_crontab).with(new_cron).returns(true)
84
+ assert @command.run
85
+ end
86
+ end
87
+
88
+ context "A command line update that contains backslashes" do
89
+ setup do
90
+ @existing = <<-EXISTING_CRON
91
+ # Begin Whenever generated tasks for: My identifier
92
+ script/runner -e production 'puts '\\''hello'\\'''
93
+ # End Whenever generated tasks for: My identifier
94
+ EXISTING_CRON
95
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
96
+ @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier')
97
+ @command.expects(:read_crontab).at_least_once.returns(@existing)
98
+ @command.expects(:whenever_cron).returns(@existing)
99
+ end
100
+
101
+ should "replace the existing block with the backslashes in tact" do
102
+ assert_equal @existing, @command.send(:updated_crontab)
103
+ end
104
+ end
105
+
106
+ context "A command line update with an identifier similar to an existing one in the crontab already" do
107
+ setup do
108
+ @existing = <<-EXISTING_CRON
109
+ # Begin Whenever generated tasks for: WheneverExisting
110
+ # End Whenever generated tasks for: WheneverExisting
111
+ EXISTING_CRON
112
+ @new = <<-NEW_CRON
113
+ # Begin Whenever generated tasks for: Whenever
114
+ # End Whenever generated tasks for: Whenever
115
+ NEW_CRON
116
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
117
+ @command = Whenever::CommandLine.new(:update => true, :identifier => 'Whenever')
118
+ @command.expects(:read_crontab).at_least_once.returns(@existing)
119
+ @command.expects(:whenever_cron).returns(@new)
120
+ end
121
+
122
+ should "append the similarly named command" do
123
+ assert_equal @existing + "\n" + @new, @command.send(:updated_crontab)
124
+ end
125
+ end
126
+
127
+ context "A command line clear" do
128
+ setup do
129
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
130
+ @command = Whenever::CommandLine.new(:clear => true, :identifier => 'My identifier')
131
+ @task = "#{two_hours} /my/command"
132
+ end
133
+
134
+ should "clear an existing block if the identifier matches" do
135
+ existing = <<-EXISTING_CRON
136
+ # Something
137
+
138
+ # Begin Whenever generated tasks for: My identifier
139
+ My whenever job that was already here
140
+ # End Whenever generated tasks for: My identifier
141
+
142
+ # Begin Whenever generated tasks for: Other identifier
143
+ This shouldn't get replaced
144
+ # End Whenever generated tasks for: Other identifier
145
+ EXISTING_CRON
146
+
147
+ @command.expects(:read_crontab).at_least_once.returns(existing)
148
+
149
+ new_cron = <<-NEW_CRON
150
+ # Something
151
+
152
+ # Begin Whenever generated tasks for: Other identifier
153
+ This shouldn't get replaced
154
+ # End Whenever generated tasks for: Other identifier
155
+ NEW_CRON
156
+
157
+ assert_equal new_cron, @command.send(:updated_crontab)
158
+
159
+ @command.expects(:write_crontab).with(new_cron).returns(true)
160
+ assert @command.run
161
+ end
162
+ end
163
+
164
+ context "A command line update with no identifier" do
165
+ setup do
166
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
167
+ Whenever::CommandLine.any_instance.expects(:default_identifier).returns('DEFAULT')
168
+ @command = Whenever::CommandLine.new(:update => true, :file => @file)
169
+ end
170
+
171
+ should "use the default identifier" do
172
+ assert_equal "Whenever generated tasks for: DEFAULT", @command.send(:comment_base)
173
+ end
174
+ end
175
+
176
+ context "combined params" do
177
+ setup do
178
+ Whenever::CommandLine.any_instance.expects(:exit)
179
+ Whenever::CommandLine.any_instance.expects(:warn)
180
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
181
+ end
182
+
183
+ should "exit with write and clear" do
184
+ @command = Whenever::CommandLine.new(:write => true, :clear => true)
185
+ end
186
+
187
+ should "exit with write and update" do
188
+ @command = Whenever::CommandLine.new(:write => true, :update => true)
189
+ end
190
+
191
+ should "exit with update and clear" do
192
+ @command = Whenever::CommandLine.new(:update => true, :clear => true)
193
+ end
194
+ end
195
+
196
+ context "A runner where the environment is overridden using the :set option" do
197
+ setup do
198
+ @output = Whenever.cron :set => 'environment=serious', :string => \
199
+ <<-file
200
+ set :job_template, nil
201
+ set :environment, :silly
202
+ set :path, '/my/path'
203
+ every 2.hours do
204
+ runner "blahblah"
205
+ end
206
+ file
207
+ end
208
+
209
+ should "output the runner using the override environment" do
210
+ assert_match two_hours + %( cd /my/path && script/runner -e serious 'blahblah'), @output
211
+ end
212
+ end
213
+
214
+ context "A runner where the environment and path are overridden using the :set option" do
215
+ setup do
216
+ @output = Whenever.cron :set => 'environment=serious&path=/serious/path', :string => \
217
+ <<-file
218
+ set :job_template, nil
219
+ set :environment, :silly
220
+ set :path, '/silly/path'
221
+ every 2.hours do
222
+ runner "blahblah"
223
+ end
224
+ file
225
+ end
226
+
227
+ should "output the runner using the overridden path and environment" do
228
+ assert_match two_hours + %( cd /serious/path && script/runner -e serious 'blahblah'), @output
229
+ end
230
+ end
231
+
232
+ context "A runner where the environment and path are overridden using the :set option with spaces in the string" do
233
+ setup do
234
+ @output = Whenever.cron :set => ' environment = serious& path =/serious/path', :string => \
235
+ <<-file
236
+ set :job_template, nil
237
+ set :environment, :silly
238
+ set :path, '/silly/path'
239
+ every 2.hours do
240
+ runner "blahblah"
241
+ end
242
+ file
243
+ end
244
+
245
+ should "output the runner using the overridden path and environment" do
246
+ assert_match two_hours + %( cd /serious/path && script/runner -e serious 'blahblah'), @output
247
+ end
248
+ end
249
+
250
+ context "A runner where the environment is overridden using the :set option but no value is given" do
251
+ setup do
252
+ @output = Whenever.cron :set => ' environment=', :string => \
253
+ <<-file
254
+ set :job_template, nil
255
+ set :environment, :silly
256
+ set :path, '/silly/path'
257
+ every 2.hours do
258
+ runner "blahblah"
259
+ end
260
+ file
261
+ end
262
+
263
+ should "output the runner using the original environmnet" do
264
+ assert_match two_hours + %( cd /silly/path && script/runner -e silly 'blahblah'), @output
265
+ end
266
+ end
267
+
268
+ context "prepare-ing the output" do
269
+ setup do
270
+ File.expects(:exists?).with('config/schedule.rb').returns(true)
271
+ end
272
+
273
+ should "not trim off the top lines of the file" do
274
+ @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier', :cut => 0)
275
+ existing = <<-EXISTING_CRON
276
+ # Useless Comments
277
+ # at the top of the file
278
+
279
+ # Begin Whenever generated tasks for: My identifier
280
+ My whenever job that was already here
281
+ # End Whenever generated tasks for: My identifier
282
+ EXISTING_CRON
283
+
284
+ assert_equal existing, @command.send(:prepare, existing)
285
+ end
286
+
287
+ should "trim off the top lines of the file" do
288
+ @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier', :cut => '3')
289
+ existing = <<-EXISTING_CRON
290
+ # Useless Comments
291
+ # at the top of the file
292
+
293
+ # Begin Whenever generated tasks for: My identifier
294
+ My whenever job that was already here
295
+ # End Whenever generated tasks for: My identifier
296
+ EXISTING_CRON
297
+
298
+ new_cron = <<-NEW_CRON
299
+ # Begin Whenever generated tasks for: My identifier
300
+ My whenever job that was already here
301
+ # End Whenever generated tasks for: My identifier
302
+ NEW_CRON
303
+
304
+ assert_equal new_cron, @command.send(:prepare, existing)
305
+ end
306
+
307
+ should "preserve terminating newlines in files" do
308
+ @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier')
309
+ existing = <<-EXISTING_CRON
310
+ # Begin Whenever generated tasks for: My identifier
311
+ My whenever job that was already here
312
+ # End Whenever generated tasks for: My identifier
313
+
314
+ # A non-Whenever task
315
+ My non-whenever job that was already here
316
+ EXISTING_CRON
317
+
318
+ assert_equal existing, @command.send(:prepare, existing)
319
+ end
320
+ end
321
+
322
+ end