whenever 0.8.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +20 -7
  4. data/Appraisals +19 -0
  5. data/CHANGELOG.md +116 -3
  6. data/Gemfile +3 -3
  7. data/LICENSE +2 -2
  8. data/README.md +133 -32
  9. data/Rakefile +3 -10
  10. data/bin/whenever +3 -0
  11. data/bin/wheneverize +8 -5
  12. data/gemfiles/activesupport4.1.gemfile +7 -0
  13. data/gemfiles/activesupport4.2.gemfile +7 -0
  14. data/gemfiles/activesupport5.0.gemfile +7 -0
  15. data/gemfiles/activesupport5.1.gemfile +7 -0
  16. data/gemfiles/activesupport5.2.gemfile +7 -0
  17. data/lib/whenever/capistrano/v2/hooks.rb +8 -0
  18. data/lib/whenever/capistrano/{recipes.rb → v2/recipes.rb} +7 -13
  19. data/lib/whenever/capistrano/{support.rb → v2/support.rb} +12 -0
  20. data/lib/whenever/capistrano/v3/tasks/whenever.rake +56 -0
  21. data/lib/whenever/capistrano.rb +5 -6
  22. data/lib/whenever/command_line.rb +69 -48
  23. data/lib/whenever/cron.rb +54 -25
  24. data/lib/whenever/job.rb +13 -14
  25. data/lib/whenever/job_list.rb +54 -24
  26. data/lib/whenever/numeric.rb +13 -0
  27. data/lib/whenever/numeric_seconds.rb +48 -0
  28. data/lib/whenever/os.rb +7 -0
  29. data/lib/whenever/output_redirection.rb +1 -0
  30. data/lib/whenever/setup.rb +19 -15
  31. data/lib/whenever/version.rb +2 -2
  32. data/lib/whenever.rb +19 -14
  33. data/test/functional/command_line_test.rb +379 -243
  34. data/test/functional/output_at_test.rb +227 -249
  35. data/test/functional/output_default_defined_jobs_test.rb +251 -193
  36. data/test/functional/output_defined_job_test.rb +65 -91
  37. data/test/functional/output_env_test.rb +22 -26
  38. data/test/functional/output_jobs_for_roles_test.rb +46 -65
  39. data/test/functional/output_jobs_with_mailto_test.rb +168 -0
  40. data/test/functional/output_redirection_test.rb +232 -291
  41. data/test/test_case.rb +32 -0
  42. data/test/test_helper.rb +44 -15
  43. data/test/unit/capistrano_support_test.rb +128 -134
  44. data/test/unit/cron_test.rb +373 -208
  45. data/test/unit/executable_test.rb +142 -0
  46. data/test/unit/job_test.rb +111 -117
  47. data/whenever.gemspec +7 -4
  48. metadata +63 -44
@@ -1,4 +1,4 @@
1
- require 'whenever/capistrano/support'
1
+ require 'whenever/capistrano/v2/support'
2
2
 
3
3
  Capistrano::Configuration.instance(:must_exist).load do
4
4
  Whenever::CapistranoSupport.load_into(self)
@@ -7,10 +7,11 @@ Capistrano::Configuration.instance(:must_exist).load do
7
7
  _cset(:whenever_options) { {:roles => fetch(:whenever_roles)} }
8
8
  _cset(:whenever_command) { "whenever" }
9
9
  _cset(:whenever_identifier) { fetch :application }
10
- _cset(:whenever_environment) { fetch :rails_env, "production" }
10
+ _cset(:whenever_environment) { fetch :rails_env, fetch(:stage, "production") }
11
11
  _cset(:whenever_variables) { "environment=#{fetch :whenever_environment}" }
12
12
  _cset(:whenever_update_flags) { "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables}" }
13
13
  _cset(:whenever_clear_flags) { "--clear-crontab #{fetch :whenever_identifier}" }
14
+ _cset(:whenever_path) { fetch :latest_release }
14
15
 
15
16
  namespace :whenever do
16
17
  desc "Update application's crontab entries using Whenever"
@@ -18,22 +19,15 @@ Capistrano::Configuration.instance(:must_exist).load do
18
19
  args = {
19
20
  :command => fetch(:whenever_command),
20
21
  :flags => fetch(:whenever_update_flags),
21
- :path => fetch(:latest_release)
22
+ :path => fetch(:whenever_path)
22
23
  }
23
24
 
24
25
  if whenever_servers.any?
26
+ args = whenever_prepare_for_rollback(args) if task_call_frames[0].task.fully_qualified_name == 'deploy:rollback'
25
27
  whenever_run_commands(args)
26
28
 
27
29
  on_rollback do
28
- if fetch(:previous_release)
29
- # rollback to the previous release's crontab
30
- args[:path] = fetch(:previous_release)
31
- else
32
- # clear the crontab if no previous release
33
- args[:path] = fetch(:release_path)
34
- args[:flags] = fetch(:whenever_clear_flags)
35
- end
36
-
30
+ args = whenever_prepare_for_rollback(args)
37
31
  whenever_run_commands(args)
38
32
  end
39
33
  end
@@ -45,7 +39,7 @@ Capistrano::Configuration.instance(:must_exist).load do
45
39
  args = {
46
40
  :command => fetch(:whenever_command),
47
41
  :flags => fetch(:whenever_clear_flags),
48
- :path => fetch(:latest_release)
42
+ :path => fetch(:whenever_path)
49
43
  }
50
44
 
51
45
  whenever_run_commands(args)
@@ -22,6 +22,18 @@ module Whenever
22
22
  end
23
23
  end
24
24
 
25
+ def whenever_prepare_for_rollback args
26
+ if fetch(:previous_release)
27
+ # rollback to the previous release's crontab
28
+ args[:path] = fetch(:previous_release)
29
+ else
30
+ # clear the crontab if no previous release
31
+ args[:path] = fetch(:release_path)
32
+ args[:flags] = fetch(:whenever_clear_flags)
33
+ end
34
+ args
35
+ end
36
+
25
37
  def whenever_run_commands(args)
26
38
  unless [:command, :path, :flags].all? { |a| args.include?(a) }
27
39
  raise ArgumentError, ":command, :path, & :flags are required"
@@ -0,0 +1,56 @@
1
+ namespace :whenever do
2
+ def setup_whenever_task(*args, &block)
3
+ args = Array(fetch(:whenever_command)) + args
4
+
5
+ on roles fetch(:whenever_roles) do |host|
6
+ args_for_host = block_given? ? args + Array(yield(host)) : args
7
+ within fetch(:whenever_path) do
8
+ with fetch(:whenever_command_environment_variables) do
9
+ execute(*args_for_host)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ def load_file
16
+ file = fetch(:whenever_load_file)
17
+ if file
18
+ "-f #{file}"
19
+ else
20
+ ''
21
+ end
22
+ end
23
+
24
+ desc "Update application's crontab entries using Whenever"
25
+ task :update_crontab do
26
+ setup_whenever_task do |host|
27
+ roles = host.roles_array.join(",")
28
+ [fetch(:whenever_update_flags), "--roles=#{roles}", load_file]
29
+ end
30
+ end
31
+
32
+ desc "Clear application's crontab entries using Whenever"
33
+ task :clear_crontab do
34
+ setup_whenever_task do |host|
35
+ [fetch(:whenever_clear_flags), load_file]
36
+ end
37
+ end
38
+
39
+ after "deploy:updated", "whenever:update_crontab"
40
+ after "deploy:reverted", "whenever:update_crontab"
41
+ end
42
+
43
+ namespace :load do
44
+ task :defaults do
45
+ set :whenever_roles, ->{ :db }
46
+ set :whenever_command, ->{ [:bundle, :exec, :whenever] }
47
+ set :whenever_command_environment_variables, ->{ fetch(:default_env).merge(rails_env: fetch(:whenever_environment)) }
48
+ set :whenever_identifier, ->{ fetch :application }
49
+ set :whenever_environment, ->{ fetch :rails_env, fetch(:stage, "production") }
50
+ set :whenever_variables, ->{ "environment=#{fetch :whenever_environment}" }
51
+ set :whenever_load_file, ->{ nil }
52
+ set :whenever_update_flags, ->{ "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables}" }
53
+ set :whenever_clear_flags, ->{ "--clear-crontab #{fetch :whenever_identifier}" }
54
+ set :whenever_path, ->{ release_path }
55
+ end
56
+ end
@@ -1,8 +1,7 @@
1
- require "whenever/capistrano/recipes"
1
+ require 'capistrano/version'
2
2
 
3
- Capistrano::Configuration.instance(:must_exist).load do
4
- # Write the new cron jobs near the end.
5
- before "deploy:finalize_update", "whenever:update_crontab"
6
- # If anything goes wrong, undo.
7
- after "deploy:rollback", "whenever:update_crontab"
3
+ if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
4
+ load File.expand_path("../capistrano/v3/tasks/whenever.rake", __FILE__)
5
+ else
6
+ require 'whenever/capistrano/v2/hooks'
8
7
  end
@@ -1,20 +1,20 @@
1
1
  require 'fileutils'
2
- require 'tempfile'
3
2
 
4
3
  module Whenever
5
4
  class CommandLine
6
5
  def self.execute(options={})
7
6
  new(options).run
8
7
  end
9
-
8
+
10
9
  def initialize(options={})
11
10
  @options = options
12
-
13
- @options[:file] ||= 'config/schedule.rb'
14
- @options[:cut] ||= 0
15
- @options[:identifier] ||= default_identifier
16
-
17
- unless File.exists?(@options[:file])
11
+
12
+ @options[:crontab_command] ||= 'crontab'
13
+ @options[:file] ||= 'config/schedule.rb'
14
+ @options[:cut] ||= 0
15
+ @options[:identifier] ||= default_identifier
16
+
17
+ if !File.exist?(@options[:file]) && @options[:clear].nil?
18
18
  warn("[fail] Can't find file: #{@options[:file]}")
19
19
  exit(1)
20
20
  end
@@ -29,8 +29,10 @@ module Whenever
29
29
  exit(1)
30
30
  end
31
31
  @options[:cut] = @options[:cut].to_i
32
+
33
+ @timestamp = Time.now.to_s
32
34
  end
33
-
35
+
34
36
  def run
35
37
  if @options[:update] || @options[:clear]
36
38
  write_crontab(updated_crontab)
@@ -43,70 +45,73 @@ module Whenever
43
45
  exit(0)
44
46
  end
45
47
  end
46
-
48
+
47
49
  protected
48
-
50
+
49
51
  def default_identifier
50
52
  File.expand_path(@options[:file])
51
53
  end
52
-
54
+
53
55
  def whenever_cron
54
56
  return '' if @options[:clear]
55
57
  @whenever_cron ||= [comment_open, Whenever.cron(@options), comment_close].compact.join("\n") + "\n"
56
58
  end
57
-
59
+
58
60
  def read_crontab
59
- return @current_crontab if @current_crontab
60
-
61
- command = ['crontab -l']
61
+ return @current_crontab if instance_variable_defined?(:@current_crontab)
62
+
63
+ command = [@options[:crontab_command]]
64
+ command << '-l'
62
65
  command << "-u #{@options[:user]}" if @options[:user]
63
-
66
+
64
67
  command_results = %x[#{command.join(' ')} 2> /dev/null]
65
68
  @current_crontab = $?.exitstatus.zero? ? prepare(command_results) : ''
66
69
  end
67
-
68
- def write_crontab(contents)
69
- tmp_cron_file = Tempfile.open('whenever_tmp_cron')
70
- tmp_cron_file << contents
71
- tmp_cron_file.fsync
72
70
 
73
- command = ['crontab']
71
+ def write_crontab(contents)
72
+ command = [@options[:crontab_command]]
74
73
  command << "-u #{@options[:user]}" if @options[:user]
75
- command << tmp_cron_file.path
74
+ # Solaris/SmartOS cron does not support the - option to read from stdin.
75
+ command << "-" unless OS.solaris?
76
+
77
+ IO.popen(command.join(' '), 'r+') do |crontab|
78
+ crontab.write(contents)
79
+ crontab.close_write
80
+ end
76
81
 
77
- if system(command.join(' '))
82
+ success = $?.exitstatus.zero?
83
+
84
+ if success
78
85
  action = 'written' if @options[:write]
79
86
  action = 'updated' if @options[:update]
80
87
  puts "[write] crontab file #{action}"
81
- tmp_cron_file.close!
82
88
  exit(0)
83
89
  else
84
90
  warn "[fail] Couldn't write crontab; try running `whenever' with no options to ensure your schedule file is valid."
85
- tmp_cron_file.close!
86
91
  exit(1)
87
92
  end
88
93
  end
89
-
90
- def updated_crontab
94
+
95
+ def updated_crontab
91
96
  # Check for unopened or unclosed identifier blocks
92
- if read_crontab =~ Regexp.new("^#{comment_open}\s*$") && (read_crontab =~ Regexp.new("^#{comment_close}\s*$")).nil?
93
- warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open}', but no '#{comment_close}'"
97
+ if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && (read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")).nil?
98
+ warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open(false)}', but no '#{comment_close(false)}'"
94
99
  exit(1)
95
- elsif (read_crontab =~ Regexp.new("^#{comment_open}\s*$")).nil? && read_crontab =~ Regexp.new("^#{comment_close}\s*$")
96
- warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close}', but no '#{comment_open}'"
100
+ elsif (read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$")).nil? && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
101
+ warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close(false)}', but no '#{comment_open(false)}'"
97
102
  exit(1)
98
103
  end
99
-
100
- # If an existing identier block is found, replace it with the new cron entries
101
- if read_crontab =~ Regexp.new("^#{comment_open}\s*$") && read_crontab =~ Regexp.new("^#{comment_close}\s*$")
104
+
105
+ # If an existing identifier block is found, replace it with the new cron entries
106
+ if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
102
107
  # If the existing crontab file contains backslashes they get lost going through gsub.
103
108
  # .gsub('\\', '\\\\\\') preserves them. Go figure.
104
- read_crontab.gsub(Regexp.new("^#{comment_open}\s*$.+^#{comment_close}\s*$", Regexp::MULTILINE), whenever_cron.chomp.gsub('\\', '\\\\\\'))
109
+ read_crontab.gsub(Regexp.new("^#{comment_open_regex}\s*$.+^#{comment_close_regex}\s*$", Regexp::MULTILINE), whenever_cron.chomp.gsub('\\', '\\\\\\'))
105
110
  else # Otherwise, append the new cron entries after any existing ones
106
111
  [read_crontab, whenever_cron].join("\n\n")
107
112
  end.gsub(/\n{3,}/, "\n\n") # More than two newlines becomes just two.
108
113
  end
109
-
114
+
110
115
  def prepare(contents)
111
116
  # Strip n lines from the top of the file as specified by the :cut option.
112
117
  # Use split with a -1 limit option to ensure the join is able to rebuild
@@ -114,21 +119,37 @@ module Whenever
114
119
  stripped_contents = contents.split($/,-1)[@options[:cut]..-1].join($/)
115
120
 
116
121
  # Some cron implementations require all non-comment lines to be newline-
117
- # terminated. (issue #95) Strip all newlines and replace with the default
122
+ # terminated. (issue #95) Strip all newlines and replace with the default
118
123
  # platform record seperator ($/)
119
124
  stripped_contents.gsub!(/\s+$/, $/)
120
125
  end
121
-
122
- def comment_base
123
- "Whenever generated tasks for: #{@options[:identifier]}"
126
+
127
+ def comment_base(include_timestamp = true)
128
+ if include_timestamp
129
+ "Whenever generated tasks for: #{@options[:identifier]} at: #{@timestamp}"
130
+ else
131
+ "Whenever generated tasks for: #{@options[:identifier]}"
132
+ end
124
133
  end
125
-
126
- def comment_open
127
- "# Begin #{comment_base}"
134
+
135
+ def comment_open(include_timestamp = true)
136
+ "# Begin #{comment_base(include_timestamp)}"
128
137
  end
129
-
130
- def comment_close
131
- "# End #{comment_base}"
138
+
139
+ def comment_close(include_timestamp = true)
140
+ "# End #{comment_base(include_timestamp)}"
141
+ end
142
+
143
+ def comment_open_regex
144
+ "#{comment_open(false)}(#{timestamp_regex}|)"
145
+ end
146
+
147
+ def comment_close_regex
148
+ "#{comment_close(false)}(#{timestamp_regex}|)"
149
+ end
150
+
151
+ def timestamp_regex
152
+ " at: \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} ([+-]\\d{4}|UTC)"
132
153
  end
133
154
  end
134
155
  end
data/lib/whenever/cron.rb CHANGED
@@ -3,16 +3,20 @@ require 'chronic'
3
3
  module Whenever
4
4
  module Output
5
5
  class Cron
6
+ DAYS = %w(sun mon tue wed thu fri sat)
7
+ MONTHS = %w(jan feb mar apr may jun jul aug sep oct nov dec)
6
8
  KEYWORDS = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
7
- REGEX = /^(@(#{KEYWORDS.join '|'})|.+\s+.+\s+.+\s+.+\s+.+.?)$/
9
+ REGEX = /^(@(#{KEYWORDS.join '|'})|((\*?[\d\/,\-]*)\s){3}(\*?([\d\/,\-]|(#{MONTHS.join '|'}))*\s)(\*?([\d\/,\-]|(#{DAYS.join '|'}))*))$/i
8
10
 
9
11
  attr_accessor :time, :task
10
12
 
11
- def initialize(time = nil, task = nil, at = nil)
13
+ def initialize(time = nil, task = nil, at = nil, options = {})
14
+ chronic_options = options[:chronic_options] || {}
15
+
12
16
  @at_given = at
13
17
  @time = time
14
18
  @task = task
15
- @at = at.is_a?(String) ? (Chronic.parse(at) || 0) : (at || 0)
19
+ @at = at.is_a?(String) ? (Chronic.parse(at, chronic_options) || 0) : (at || 0)
16
20
  end
17
21
 
18
22
  def self.enumerate(item, detect_cron = true)
@@ -30,10 +34,10 @@ module Whenever
30
34
  items
31
35
  end
32
36
 
33
- def self.output(times, job)
37
+ def self.output(times, job, options = {})
34
38
  enumerate(times).each do |time|
35
39
  enumerate(job.at, false).each do |at|
36
- yield new(time, job.output, at).output
40
+ yield new(time, job.output, at, options).output
37
41
  end
38
42
  end
39
43
  end
@@ -43,8 +47,9 @@ module Whenever
43
47
  end
44
48
 
45
49
  def time_in_cron_syntax
50
+ @time = @time.to_i if @time.is_a?(Numeric) # Compatibility with `1.day` format using ruby 2.3 and activesupport
46
51
  case @time
47
- when REGEX then @time # raw cron sytax given
52
+ when REGEX then @time # raw cron syntax given
48
53
  when Symbol then parse_symbol
49
54
  when String then parse_as_string
50
55
  else parse_time
@@ -53,18 +58,18 @@ module Whenever
53
58
 
54
59
  protected
55
60
  def day_given?
56
- months = %w(jan feb mar apr may jun jul aug sep oct nov dec)
57
- @at_given.is_a?(String) && months.any? { |m| @at_given.downcase.index(m) }
61
+ @at_given.is_a?(String) && (MONTHS.any? { |m| @at_given.downcase.index(m) } || @at_given[/\d\/\d/])
58
62
  end
59
63
 
60
64
  def parse_symbol
61
65
  shortcut = case @time
62
66
  when *KEYWORDS then "@#{@time}" # :reboot => '@reboot'
63
- when :year then 12.months
64
- when :day then 1.day
65
- when :month then 1.month
66
- when :week then 1.week
67
- when :hour then 1.hour
67
+ when :year then Whenever.seconds(1, :year)
68
+ when :day then Whenever.seconds(1, :day)
69
+ when :month then Whenever.seconds(1, :month)
70
+ when :week then Whenever.seconds(1, :week)
71
+ when :hour then Whenever.seconds(1, :hour)
72
+ when :minute then Whenever.seconds(1, :minute)
68
73
  end
69
74
 
70
75
  if shortcut.is_a?(Numeric)
@@ -84,30 +89,43 @@ module Whenever
84
89
  def parse_time
85
90
  timing = Array.new(5, '*')
86
91
  case @time
87
- when 0.seconds...1.minute
92
+ when Whenever.seconds(0, :seconds)...Whenever.seconds(1, :minute)
88
93
  raise ArgumentError, "Time must be in minutes or higher"
89
- when 1.minute...1.hour
94
+ when Whenever.seconds(1, :minute)...Whenever.seconds(1, :hour)
90
95
  minute_frequency = @time / 60
91
96
  timing[0] = comma_separated_timing(minute_frequency, 59, @at || 0)
92
- when 1.hour...1.day
97
+ when Whenever.seconds(1, :hour)...Whenever.seconds(1, :day)
93
98
  hour_frequency = (@time / 60 / 60).round
94
- timing[0] = @at.is_a?(Time) ? @at.min : @at
99
+ timing[0] = @at.is_a?(Time) ? @at.min : range_or_integer(@at, 0..59, 'Minute')
95
100
  timing[1] = comma_separated_timing(hour_frequency, 23)
96
- when 1.day...1.month
101
+ when Whenever.seconds(1, :day)...Whenever.seconds(1, :month)
97
102
  day_frequency = (@time / 24 / 60 / 60).round
98
103
  timing[0] = @at.is_a?(Time) ? @at.min : 0
99
- timing[1] = @at.is_a?(Time) ? @at.hour : @at
104
+ timing[1] = @at.is_a?(Time) ? @at.hour : range_or_integer(@at, 0..23, 'Hour')
100
105
  timing[2] = comma_separated_timing(day_frequency, 31, 1)
101
- when 1.month..12.months
102
- month_frequency = (@time / 30 / 24 / 60 / 60).round
106
+ when Whenever.seconds(1, :month)...Whenever.seconds(1, :year)
107
+ month_frequency = (@time / 30 / 24 / 60 / 60).round
103
108
  timing[0] = @at.is_a?(Time) ? @at.min : 0
104
109
  timing[1] = @at.is_a?(Time) ? @at.hour : 0
105
110
  timing[2] = if @at.is_a?(Time)
106
111
  day_given? ? @at.day : 1
107
112
  else
108
- @at.zero? ? 1 : @at
113
+ @at == 0 ? 1 : range_or_integer(@at, 1..31, 'Day')
109
114
  end
110
115
  timing[3] = comma_separated_timing(month_frequency, 12, 1)
116
+ when Whenever.seconds(1, :year)
117
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
118
+ timing[1] = @at.is_a?(Time) ? @at.hour : 0
119
+ timing[2] = if @at.is_a?(Time)
120
+ day_given? ? @at.day : 1
121
+ else
122
+ 1
123
+ end
124
+ timing[3] = if @at.is_a?(Time)
125
+ day_given? ? @at.month : 1
126
+ else
127
+ @at == 0 ? 1 : range_or_integer(@at, 1..12, 'Month')
128
+ end
111
129
  else
112
130
  return parse_as_string
113
131
  end
@@ -125,15 +143,26 @@ module Whenever
125
143
  return (timing << '1-5') * " " if string.downcase.index('weekday')
126
144
  return (timing << '6,0') * " " if string.downcase.index('weekend')
127
145
 
128
- %w(sun mon tue wed thu fri sat).each_with_index do |day, i|
146
+ DAYS.each_with_index do |day, i|
129
147
  return (timing << i) * " " if string.downcase.index(day)
130
148
  end
131
149
 
132
- raise ArgumentError, "Couldn't parse: #{@time}"
150
+ raise ArgumentError, "Couldn't parse: #{@time.inspect}"
151
+ end
152
+
153
+ def range_or_integer(at, valid_range, name)
154
+ must_be_between = "#{name} must be between #{valid_range.min}-#{valid_range.max}"
155
+ if at.is_a?(Range)
156
+ raise ArgumentError, "#{must_be_between}, #{at.min} given" unless valid_range.include?(at.min)
157
+ raise ArgumentError, "#{must_be_between}, #{at.max} given" unless valid_range.include?(at.max)
158
+ return "#{at.min}-#{at.max}"
159
+ end
160
+ raise ArgumentError, "#{must_be_between}, #{at} given" unless valid_range.include?(at)
161
+ at
133
162
  end
134
163
 
135
164
  def comma_separated_timing(frequency, max, start = 0)
136
- return start if frequency.blank? || frequency.zero?
165
+ return start if frequency.nil? || frequency == "" || frequency.zero?
137
166
  return '*' if frequency == 1
138
167
  return frequency if frequency > (max * 0.5).ceil
139
168
 
data/lib/whenever/job.rb CHANGED
@@ -2,25 +2,24 @@ require 'shellwords'
2
2
 
3
3
  module Whenever
4
4
  class Job
5
- attr_reader :at, :roles
5
+ attr_reader :at, :roles, :mailto
6
6
 
7
7
  def initialize(options = {})
8
8
  @options = options
9
- @at = options.delete(:at)
10
- @template = options.delete(:template)
11
- @job_template = options.delete(:job_template) || ":job"
12
- @roles = Array.wrap(options.delete(:roles))
13
- @options[:output] = options.has_key?(:output) ? Whenever::Output::Redirection.new(options[:output]).to_s : ''
14
- @options[:environment] ||= :production
15
- @options[:path] = Shellwords.shellescape(@options[:path] || Whenever.path)
9
+ @at = options.delete(:at)
10
+ @template = options.delete(:template)
11
+ @mailto = options.fetch(:mailto, :default_mailto)
12
+ @job_template = options.delete(:job_template) || ":job"
13
+ @roles = Array(options.delete(:roles))
14
+ @options[:output] = options.has_key?(:output) ? Whenever::Output::Redirection.new(options[:output]).to_s : ''
15
+ @options[:environment_variable] ||= "RAILS_ENV"
16
+ @options[:environment] ||= :production
17
+ @options[:path] = Shellwords.shellescape(@options[:path] || Whenever.path)
16
18
  end
17
19
 
18
20
  def output
19
- job = process_template(@template, @options).strip
20
- out = process_template(@job_template, { :job => job }).strip
21
- if out =~ /\n/
22
- raise ArgumentError, "Task contains newline"
23
- end
21
+ job = process_template(@template, @options)
22
+ out = process_template(@job_template, @options.merge(:job => job))
24
23
  out.gsub(/%/, '\%')
25
24
  end
26
25
 
@@ -42,7 +41,7 @@ module Whenever
42
41
  else
43
42
  option
44
43
  end
45
- end
44
+ end.gsub(/\s+/m, " ").strip
46
45
  end
47
46
 
48
47
  def escape_single_quotes(str)