whenever 0.8.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +20 -7
- data/Appraisals +19 -0
- data/CHANGELOG.md +116 -3
- data/Gemfile +3 -3
- data/LICENSE +2 -2
- data/README.md +133 -32
- data/Rakefile +3 -10
- data/bin/whenever +3 -0
- data/bin/wheneverize +8 -5
- data/gemfiles/activesupport4.1.gemfile +7 -0
- data/gemfiles/activesupport4.2.gemfile +7 -0
- data/gemfiles/activesupport5.0.gemfile +7 -0
- data/gemfiles/activesupport5.1.gemfile +7 -0
- data/gemfiles/activesupport5.2.gemfile +7 -0
- data/lib/whenever/capistrano/v2/hooks.rb +8 -0
- data/lib/whenever/capistrano/{recipes.rb → v2/recipes.rb} +7 -13
- data/lib/whenever/capistrano/{support.rb → v2/support.rb} +12 -0
- data/lib/whenever/capistrano/v3/tasks/whenever.rake +56 -0
- data/lib/whenever/capistrano.rb +5 -6
- data/lib/whenever/command_line.rb +69 -48
- data/lib/whenever/cron.rb +54 -25
- data/lib/whenever/job.rb +13 -14
- data/lib/whenever/job_list.rb +54 -24
- data/lib/whenever/numeric.rb +13 -0
- data/lib/whenever/numeric_seconds.rb +48 -0
- data/lib/whenever/os.rb +7 -0
- data/lib/whenever/output_redirection.rb +1 -0
- data/lib/whenever/setup.rb +19 -15
- data/lib/whenever/version.rb +2 -2
- data/lib/whenever.rb +19 -14
- data/test/functional/command_line_test.rb +379 -243
- data/test/functional/output_at_test.rb +227 -249
- data/test/functional/output_default_defined_jobs_test.rb +251 -193
- data/test/functional/output_defined_job_test.rb +65 -91
- data/test/functional/output_env_test.rb +22 -26
- data/test/functional/output_jobs_for_roles_test.rb +46 -65
- data/test/functional/output_jobs_with_mailto_test.rb +168 -0
- data/test/functional/output_redirection_test.rb +232 -291
- data/test/test_case.rb +32 -0
- data/test/test_helper.rb +44 -15
- data/test/unit/capistrano_support_test.rb +128 -134
- data/test/unit/cron_test.rb +373 -208
- data/test/unit/executable_test.rb +142 -0
- data/test/unit/job_test.rb +111 -117
- data/whenever.gemspec +7 -4
- 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(:
|
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
|
-
|
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(:
|
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
|
data/lib/whenever/capistrano.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require 'capistrano/version'
|
2
2
|
|
3
|
-
Capistrano::
|
4
|
-
|
5
|
-
|
6
|
-
|
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[:
|
14
|
-
@options[:
|
15
|
-
@options[:
|
16
|
-
|
17
|
-
|
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
|
60
|
-
|
61
|
-
command = [
|
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
|
-
|
71
|
+
def write_crontab(contents)
|
72
|
+
command = [@options[:crontab_command]]
|
74
73
|
command << "-u #{@options[:user]}" if @options[:user]
|
75
|
-
|
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
|
-
|
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("^#{
|
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("^#{
|
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
|
101
|
-
if read_crontab =~ Regexp.new("^#{
|
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("^#{
|
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
|
-
|
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 '|'})
|
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
|
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
|
-
|
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
|
64
|
-
when :day then 1
|
65
|
-
when :month then 1
|
66
|
-
when :week then 1
|
67
|
-
when :hour then 1
|
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
|
92
|
+
when Whenever.seconds(0, :seconds)...Whenever.seconds(1, :minute)
|
88
93
|
raise ArgumentError, "Time must be in minutes or higher"
|
89
|
-
when 1
|
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
|
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
|
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
|
102
|
-
month_frequency = (@time / 30
|
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
|
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
|
-
|
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.
|
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
|
10
|
-
@template
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@options[:
|
15
|
-
@options[:
|
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)
|
20
|
-
out = process_template(@job_template,
|
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)
|