lowang-whenever 0.7.0.1

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