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