whenever 0.8.2 → 1.0.0

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 (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)