le1t0-whenever 0.4.2.003 → 0.6.2.001

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,13 @@
1
1
  module Whenever
2
2
  module Output
3
-
4
3
  class Cron
5
4
 
6
5
  attr_accessor :time, :task
7
6
 
8
- def initialize(time = nil, task = nil, at = nil, output_redirection = nil)
7
+ def initialize(time = nil, task = nil, at = nil)
9
8
  @time = time
10
9
  @task = task
11
10
  @at = at.is_a?(String) ? (Chronic.parse(at) || 0) : (at || 0)
12
- @output_redirection = output_redirection
13
11
  end
14
12
 
15
13
  def self.enumerate(item)
@@ -25,13 +23,13 @@ module Whenever
25
23
  def self.output(times, job)
26
24
  enumerate(times).each do |time|
27
25
  enumerate(job.at).each do |at|
28
- yield new(time, job.output, at, job.output_redirection).output
26
+ yield new(time, job.output, at).output
29
27
  end
30
28
  end
31
29
  end
32
30
 
33
31
  def output
34
- [time_in_cron_syntax, task, output_redirection].compact.join(' ').strip
32
+ [time_in_cron_syntax, task].compact.join(' ').strip
35
33
  end
36
34
 
37
35
  def time_in_cron_syntax
@@ -41,10 +39,6 @@ module Whenever
41
39
  else parse_time
42
40
  end
43
41
  end
44
-
45
- def output_redirection
46
- OutputRedirection.new(@output_redirection).to_s unless @output_redirection == :not_set
47
- end
48
42
 
49
43
  protected
50
44
 
@@ -60,7 +54,7 @@ module Whenever
60
54
  end
61
55
 
62
56
  if shortcut
63
- if @at.is_a?(Time) || (@at.is_a?(Numeric) && @at>0)
57
+ if @at.is_a?(Time) || (@at.is_a?(Numeric) && @at > 0)
64
58
  raise ArgumentError, "You cannot specify an ':at' when using the shortcuts for times."
65
59
  else
66
60
  return shortcut
@@ -77,7 +71,7 @@ module Whenever
77
71
  raise ArgumentError, "Time must be in minutes or higher"
78
72
  when 1.minute...1.hour
79
73
  minute_frequency = @time / 60
80
- timing[0] = comma_separated_timing(minute_frequency, 59)
74
+ timing[0] = comma_separated_timing(minute_frequency, 59, @at || 0)
81
75
  when 1.hour...1.day
82
76
  hour_frequency = (@time / 60 / 60).round
83
77
  timing[0] = @at.is_a?(Time) ? @at.min : @at
@@ -134,6 +128,5 @@ module Whenever
134
128
  end
135
129
 
136
130
  end
137
-
138
131
  end
139
132
  end
@@ -0,0 +1,49 @@
1
+ module Whenever
2
+ class Job
3
+
4
+ attr_reader :at
5
+
6
+ def initialize(options = {})
7
+ @options = options
8
+ @at = options.delete(:at)
9
+ @template = options.delete(:template)
10
+ @job_template = options.delete(:job_template) || ":job"
11
+ @options[:output] = Whenever::Output::Redirection.new(options[:output]).to_s if options.has_key?(:output)
12
+ @options[:environment] ||= :production
13
+ @options[:path] ||= Whenever.path
14
+ end
15
+
16
+ def output
17
+ job = process_template(@template, @options).strip
18
+ process_template(@job_template, { :job => job }).strip
19
+ end
20
+
21
+ protected
22
+
23
+ def process_template(template, options)
24
+ template.gsub(/:\w+/) do |key|
25
+ before_and_after = [$`[-1..-1], $'[0..0]]
26
+ option = options[key.sub(':', '').to_sym]
27
+
28
+ if before_and_after.all? { |c| c == "'" }
29
+ escape_single_quotes(option)
30
+ elsif before_and_after.all? { |c| c == '"' }
31
+ escape_double_quotes(option)
32
+ elsif option.is_a?(Hash)
33
+ option.collect do |key, value| "#{key}=\"#{value}\"" end.join(' ')
34
+ else
35
+ option
36
+ end
37
+ end
38
+ end
39
+
40
+ def escape_single_quotes(str)
41
+ str.gsub(/'/) { "'\\''" }
42
+ end
43
+
44
+ def escape_double_quotes(str)
45
+ str.gsub(/"/) { '\"' }
46
+ end
47
+
48
+ end
49
+ end
@@ -2,8 +2,7 @@ module Whenever
2
2
  class JobList
3
3
 
4
4
  def initialize(options)
5
- @jobs = Hash.new
6
- @env = Hash.new
5
+ @jobs, @env, @set_variables, @pre_set_variables = {}, {}, {}, {}
7
6
 
8
7
  case options
9
8
  when String
@@ -16,15 +15,19 @@ module Whenever
16
15
  end
17
16
  pre_set(options[:set])
18
17
  end
19
-
20
- eval(config)
18
+
19
+ setup = File.read("#{File.expand_path(File.dirname(__FILE__))}/setup.rb")
20
+
21
+ eval(setup + config)
21
22
  end
22
23
 
23
24
  def set(variable, value)
24
- return if instance_variable_defined?("@#{variable}".to_sym)
25
+ variable = variable.to_sym
26
+ return if @pre_set_variables[variable]
25
27
 
26
28
  instance_variable_set("@#{variable}".to_sym, value)
27
29
  self.class.send(:attr_reader, variable.to_sym)
30
+ @set_variables[variable] = value
28
31
  end
29
32
 
30
33
  def env(variable, value)
@@ -37,31 +40,24 @@ module Whenever
37
40
  yield
38
41
  end
39
42
 
40
- def command(task, options = {})
41
- # :cron_log was an old option for output redirection, it remains for backwards compatibility
42
- options[:output] = (options[:cron_log] || @cron_log) if defined?(@cron_log) || options.has_key?(:cron_log)
43
- # :output is the newer, more flexible option.
44
- options[:output] = @output if defined?(@output) && !options.has_key?(:output)
45
- options[:class] ||= Whenever::Job::Default
46
- @jobs[@current_time_scope] ||= []
47
- @jobs[@current_time_scope] << options[:class].new(@options.merge(:task => task).merge(options))
48
- end
49
-
50
- def runner(task, options = {})
51
- options.reverse_merge!(:environment => @environment, :path => @path)
52
- options[:class] = Whenever::Job::Runner
53
- command(task, options)
54
- end
55
-
56
- def rake(task, options = {})
57
- options.reverse_merge!(:environment => @environment, :path => @path)
58
- options[:class] = Whenever::Job::RakeTask
59
- command(task, options)
43
+ def job_type(name, template)
44
+ class_eval do
45
+ define_method(name) do |task, *args|
46
+ options = { :task => task, :template => template }
47
+ options.merge!(args[0]) if args[0].is_a? Hash
48
+
49
+ # :cron_log was an old option for output redirection, it remains for backwards compatibility
50
+ options[:output] = (options[:cron_log] || @cron_log) if defined?(@cron_log) || options.has_key?(:cron_log)
51
+ # :output is the newer, more flexible option.
52
+ options[:output] = @output if defined?(@output) && !options.has_key?(:output)
53
+
54
+ @jobs[@current_time_scope] ||= []
55
+ @jobs[@current_time_scope] << Whenever::Job.new(@options.merge(@set_variables).merge(options))
56
+ end
57
+ end
60
58
  end
61
59
 
62
- def generate_cron_output
63
- set_path_environment_variable
64
-
60
+ def generate_cron_output
65
61
  [environment_variables, cron_jobs].compact.join
66
62
  end
67
63
 
@@ -79,22 +75,13 @@ module Whenever
79
75
  pairs.each do |pair|
80
76
  next unless pair.index('=')
81
77
  variable, value = *pair.split('=')
82
- set(variable.strip, value.strip) unless variable.blank? || value.blank?
78
+ unless variable.blank? || value.blank?
79
+ variable = variable.strip.to_sym
80
+ set(variable, value.strip)
81
+ @pre_set_variables[variable] = value
82
+ end
83
83
  end
84
84
  end
85
-
86
- def set_path_environment_variable
87
- return if path_should_not_be_set_automatically?
88
- @env[:PATH] = read_path unless read_path.blank?
89
- end
90
-
91
- def read_path
92
- ENV['PATH'] if ENV
93
- end
94
-
95
- def path_should_not_be_set_automatically?
96
- @set_path_automatically === false || @env[:PATH] || @env["PATH"]
97
- end
98
85
 
99
86
  def environment_variables
100
87
  return if @env.empty?
@@ -0,0 +1,58 @@
1
+ module Whenever
2
+ module Output
3
+ class Redirection
4
+
5
+ def initialize(output)
6
+ @output = output
7
+ end
8
+
9
+ def to_s
10
+ return '' unless defined?(@output)
11
+ case @output
12
+ when String then redirect_from_string
13
+ when Hash then redirect_from_hash
14
+ when NilClass then ">> /dev/null 2>&1"
15
+ else ''
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ def stdout
22
+ return unless @output.has_key?(:standard)
23
+ @output[:standard].nil? ? '/dev/null' : @output[:standard]
24
+ end
25
+
26
+ def stderr
27
+ return unless @output.has_key?(:error)
28
+ @output[:error].nil? ? '/dev/null' : @output[:error]
29
+ end
30
+
31
+ def redirect_from_hash
32
+ case
33
+ when stdout == '/dev/null' && stderr == '/dev/null'
34
+ "> /dev/null 2>&1"
35
+ when stdout && stderr == '/dev/null'
36
+ ">> #{stdout} 2> /dev/null"
37
+ when stdout && stderr
38
+ ">> #{stdout} 2>> #{stderr}"
39
+ when stderr == '/dev/null'
40
+ "2> /dev/null"
41
+ when stderr
42
+ "2>> #{stderr}"
43
+ when stdout == '/dev/null'
44
+ "> /dev/null"
45
+ when stdout
46
+ ">> #{stdout}"
47
+ else
48
+ ''
49
+ end
50
+ end
51
+
52
+ def redirect_from_string
53
+ ">> #{@output} 2>&1"
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,18 @@
1
+ # Environemnt 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, ":envvars :task :output"
11
+ job_type :rake, "cd :path && :envvars RAILS_ENV=:environment rake :task --silent :output"
12
+
13
+ # Create a runner job that's appropriate for the Rails version,
14
+ if File.exists?(File.join(path, 'script', 'rails'))
15
+ job_type :runner, "cd :path && :envvars script/rails runner -e :environment ':task' :output"
16
+ else
17
+ job_type :runner, "cd :path && :envvars script/runner -e :environment ':task' :output"
18
+ end
@@ -1,3 +1,3 @@
1
1
  module Whenever
2
- VERSION = '0.4.2.003'
2
+ VERSION = '0.6.2.001'
3
3
  end unless defined?(Whenever::VERSION)
@@ -0,0 +1,310 @@
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
+ # here-doc adds an extra newline we need removed
285
+ assert_equal existing.strip, @command.send(:prepare, existing)
286
+ end
287
+
288
+ should "trim off the top lines of the file" do
289
+ @command = Whenever::CommandLine.new(:update => true, :identifier => 'My identifier', :cut => '3')
290
+ existing = <<-EXISTING_CRON
291
+ # Useless Comments
292
+ # at the top of the file
293
+
294
+ # Begin Whenever generated tasks for: My identifier
295
+ My whenever job that was already here
296
+ # End Whenever generated tasks for: My identifier
297
+ EXISTING_CRON
298
+
299
+ new_cron = <<-NEW_CRON
300
+ # Begin Whenever generated tasks for: My identifier
301
+ My whenever job that was already here
302
+ # End Whenever generated tasks for: My identifier
303
+ NEW_CRON
304
+
305
+ # here-doc adds an extra newline we need removed
306
+ assert_equal new_cron.strip, @command.send(:prepare, existing)
307
+ end
308
+ end
309
+
310
+ end