whenever 0.9.7 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +8 -5
- data/CHANGELOG.md +14 -0
- data/LICENSE +1 -1
- data/README.md +75 -10
- data/bin/whenever +3 -0
- data/bin/wheneverize +5 -2
- data/lib/whenever/capistrano/v2/recipes.rb +3 -2
- data/lib/whenever/capistrano/v3/tasks/whenever.rake +17 -4
- data/lib/whenever/command_line.rb +38 -18
- data/lib/whenever/cron.rb +32 -11
- data/lib/whenever/job.rb +2 -1
- data/lib/whenever/job_list.rb +50 -20
- data/lib/whenever/setup.rb +4 -0
- data/lib/whenever/version.rb +1 -1
- data/test/functional/command_line_test.rb +159 -32
- data/test/functional/output_at_test.rb +39 -0
- data/test/functional/output_jobs_with_mailto_test.rb +168 -0
- data/test/test_helper.rb +18 -8
- data/test/unit/cron_test.rb +130 -4
- data/test/unit/executable_test.rb +142 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c8fe56b21ffa57026a2b944ae208bea62a9d7e9
|
4
|
+
data.tar.gz: 543d704698ab03d944679daa1e85bee1b3936286
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d0bd440a4beab5ecde93ccb91fad7cf5b2bf2ffe14a376ad24392fe5b4a3d53ba1d7ce15a53d2debc3b639a35f609436d21ab78de16b99a857fe1e16ed8bed1
|
7
|
+
data.tar.gz: 4bb8692d2f7130e45d80fc05a6a54609fd160155014eae330da8f0fc9ed97e9338d523282cd4f8ec197531231493bd04d2409a3d7a6bbc57761ff6ad0ff7f422
|
data/.travis.yml
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
language: ruby
|
2
|
-
script: bundle exec rake
|
3
2
|
|
4
3
|
before_install:
|
5
4
|
- gem install bundler
|
@@ -7,12 +6,16 @@ before_install:
|
|
7
6
|
rvm:
|
8
7
|
- 1.9.3
|
9
8
|
- 2.0.0
|
10
|
-
- 2.1.
|
11
|
-
- 2.2.
|
12
|
-
- 2.3.
|
13
|
-
-
|
9
|
+
- 2.1.10
|
10
|
+
- 2.2.6
|
11
|
+
- 2.3.3
|
12
|
+
- 2.4.0
|
13
|
+
- jruby-9.1.9.0
|
14
14
|
|
15
15
|
gemfile:
|
16
16
|
- Gemfile
|
17
17
|
- gemfiles/activesupport4.1.gemfile
|
18
18
|
- gemfiles/activesupport4.2.gemfile
|
19
|
+
env:
|
20
|
+
global:
|
21
|
+
- JRUBY_OPTS=--debug
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
### develop
|
2
2
|
|
3
|
+
### 0.10.0 / November 19, 2017
|
4
|
+
|
5
|
+
* Modify wheneverize to allow for the creating of 'config' directory when not present
|
6
|
+
|
7
|
+
* Add --crontab-command to whenever binary for overriding the crontab command. [Martin Grandrath]
|
8
|
+
|
9
|
+
* Allow setting the path within which Capistrano will execute whenever. [Samuel Johnson](https://github.com/javan/whenever/pull/619)
|
10
|
+
|
11
|
+
* Allow the use of string literals for month and day-of-week in raw cron syntax.. [Potamianos Gregory](https://github.com/javan/whenever/pull/711)
|
12
|
+
|
13
|
+
* Include Capistrano default environment variables when executing Whenever. [Karl Li](https://github.com/javan/whenever/pull/719)
|
14
|
+
|
15
|
+
* Allow configuring an alternative schedule file in Capistrano. [Shinichi Okamoto](https://github.com/javan/whenever/pull/666)
|
16
|
+
|
3
17
|
### 0.9.7 / June 14, 2016
|
4
18
|
|
5
19
|
* Restore compatibility with Capistrano v3; it has a bug which we have to work around [Ben Langfeld, Chris Gunther, Shohei Yamasaki]
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -19,7 +19,7 @@ $ cd /apps/my-great-project
|
|
19
19
|
$ wheneverize .
|
20
20
|
```
|
21
21
|
|
22
|
-
This will create an initial `config/schedule.rb` file for you.
|
22
|
+
This will create an initial `config/schedule.rb` file for you (as long as the config folder is already present in your project).
|
23
23
|
|
24
24
|
### The `whenever` command
|
25
25
|
|
@@ -34,14 +34,21 @@ This will simply show you your `schedule.rb` file converted to cron syntax. It d
|
|
34
34
|
$ whenever --update-crontab
|
35
35
|
```
|
36
36
|
|
37
|
+
Other commonly used options include:
|
38
|
+
```sh
|
39
|
+
$ whenever --user app # set a user as which to install the crontab
|
40
|
+
$ whenever --load-file config/my_schedule.rb # set the schedule file
|
41
|
+
$ whenever --crontab-command 'sudo crontab` # override the crontab command
|
42
|
+
```
|
43
|
+
|
37
44
|
You can list installed cron jobs using `crontab -l`.
|
38
45
|
|
39
|
-
Run `whenever --help` for a complete list of options for selecting the schedule to use, setting variables in the schedule,
|
46
|
+
Run `whenever --help` for a complete list of options for selecting the schedule to use, setting variables in the schedule, etc.
|
40
47
|
|
41
48
|
### Example schedule.rb file
|
42
49
|
|
43
50
|
```ruby
|
44
|
-
every 3.hours do
|
51
|
+
every 3.hours do # 1.minute 1.day 1.week 1.month 1.year is also supported
|
45
52
|
runner "MyModel.some_process"
|
46
53
|
rake "my:rake:task"
|
47
54
|
command "/usr/bin/my_great_command"
|
@@ -113,6 +120,57 @@ Or set the job_template to nil to have your jobs execute normally.
|
|
113
120
|
set :job_template, nil
|
114
121
|
```
|
115
122
|
|
123
|
+
### Parsing dates and times
|
124
|
+
|
125
|
+
Whenever uses the [Chronic](https://github.com/mojombo/chronic) gem to parse the specified dates and times.
|
126
|
+
|
127
|
+
You can set your custom Chronic configuration if the defaults don't fit you.
|
128
|
+
|
129
|
+
For example, to assume a 24 hour clock instead of the default 12 hour clock:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
set :chronic_options, :hours24 => true
|
133
|
+
|
134
|
+
# By default this would run the job every day at 3am
|
135
|
+
every 1.day, :at => '3:00' do
|
136
|
+
runner "MyModel.nightly_archive_job"
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
You can see a list of all available options here: <https://github.com/mojombo/chronic/blob/master/lib/chronic/parser.rb>
|
141
|
+
|
142
|
+
### Customize email recipient with the `MAILTO` environment variable
|
143
|
+
|
144
|
+
Output from the jobs is sent to the email address configured in the `MAILTO` environment variable.
|
145
|
+
|
146
|
+
There are many ways to further configure the recipient.
|
147
|
+
|
148
|
+
Example: A global configuration, overriding the environment's value:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
env 'MAILTO', 'output_of_cron@example.com'
|
152
|
+
|
153
|
+
every 3.hours do
|
154
|
+
command "/usr/bin/my_great_command"
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
Example: A `MAILTO` configured for all the jobs in an interval block:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
every 3.hours, mailto: 'my_super_command@example.com' do
|
162
|
+
command "/usr/bin/my_super_command"
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
Example: A `MAILTO` configured for a single job:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
every 3.hours do
|
170
|
+
command "/usr/bin/my_super_command", mailto: 'my_super_command_output@example.com'
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
116
174
|
### Capistrano integration
|
117
175
|
|
118
176
|
Use the built-in Capistrano recipe for easy crontab updates with deploys. For Capistrano V3, see the next section.
|
@@ -140,7 +198,7 @@ require "whenever/capistrano"
|
|
140
198
|
|
141
199
|
The capistrano variable `:stage` should be the one holding your environment name. This will make the correct `:environment` available in your `schedule.rb`.
|
142
200
|
|
143
|
-
If both your environments are on the same server you'll want to namespace them or they'll overwrite each other when you deploy:
|
201
|
+
If both your environments are on the same server you'll want to namespace them, or they'll overwrite each other when you deploy:
|
144
202
|
|
145
203
|
```ruby
|
146
204
|
set :whenever_environment, defer { stage }
|
@@ -148,6 +206,13 @@ set :whenever_identifier, defer { "#{application}_#{stage}" }
|
|
148
206
|
require "whenever/capistrano"
|
149
207
|
```
|
150
208
|
|
209
|
+
If you use a schedule at an alternative path, you may configure it like so:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
set :whenever_load_file, defer { "#{release_path}/somewhere/else/schedule.rb" }
|
213
|
+
require "whenever/capistrano"
|
214
|
+
```
|
215
|
+
|
151
216
|
### Capistrano V3 Integration
|
152
217
|
|
153
218
|
In your "Capfile" file:
|
@@ -172,10 +237,10 @@ different servers in your capistrano deployment, then you can safely stop readin
|
|
172
237
|
now and everything should just work the same way it always has.
|
173
238
|
|
174
239
|
When you define a job in your schedule.rb file, by default it will be deployed to
|
175
|
-
all servers in the whenever_roles list (which defaults to [:db]).
|
240
|
+
all servers in the whenever_roles list (which defaults to `[:db]`).
|
176
241
|
|
177
242
|
However, if you want to restrict certain jobs to only run on subset of servers,
|
178
|
-
you can add a
|
243
|
+
you can add a `:roles => [...]` argument to their definitions. **Make sure to add
|
179
244
|
that role to the whenever_roles list in your deploy.rb.**
|
180
245
|
|
181
246
|
When you run `cap deploy`, jobs with a :roles list specified will only be added to
|
@@ -184,8 +249,8 @@ the crontabs on servers with one or more of the roles in that list.
|
|
184
249
|
Jobs with no :roles argument will be deployed to all servers in the whenever_roles
|
185
250
|
list. This is to maintain backward compatibility with previous releases of whenever.
|
186
251
|
|
187
|
-
So, for example, with the default whenever_roles of [:db]
|
188
|
-
deployed to all servers with the
|
252
|
+
So, for example, with the default whenever_roles of `[:db]`, a job like this would be
|
253
|
+
deployed to all servers with the `:db` role:
|
189
254
|
|
190
255
|
```ruby
|
191
256
|
every :day, :at => '12:20am' do
|
@@ -193,7 +258,7 @@ every :day, :at => '12:20am' do
|
|
193
258
|
end
|
194
259
|
```
|
195
260
|
|
196
|
-
If we set whenever_roles to [:db, :app] in deploy.rb, and have the following
|
261
|
+
If we set whenever_roles to `[:db, :app]` in deploy.rb, and have the following
|
197
262
|
jobs in schedule.rb:
|
198
263
|
|
199
264
|
```ruby
|
@@ -257,4 +322,4 @@ It's a little bit dated now, but remains a good introduction.
|
|
257
322
|
|
258
323
|
----
|
259
324
|
|
260
|
-
Copyright ©
|
325
|
+
Copyright © 2017 Javan Makhmali
|
data/bin/whenever
CHANGED
@@ -35,6 +35,9 @@ OptionParser.new do |opts|
|
|
35
35
|
opts.on('-r', '--roles [role1,role2]', 'Comma-separated list of server roles to generate cron jobs for') do |roles|
|
36
36
|
options[:roles] = roles.split(',').map(&:to_sym) if roles
|
37
37
|
end
|
38
|
+
opts.on('-x', '--crontab-command [command]', 'Default: crontab') do |crontab_command|
|
39
|
+
options[:crontab_command] = crontab_command if crontab_command
|
40
|
+
end
|
38
41
|
opts.on('-v', '--version') { puts "Whenever v#{Whenever::VERSION}"; exit(0) }
|
39
42
|
end.parse!
|
40
43
|
|
data/bin/wheneverize
CHANGED
@@ -58,9 +58,12 @@ if File.exist?(file)
|
|
58
58
|
warn "[skip] `#{file}' already exists"
|
59
59
|
elsif File.exist?(file.downcase)
|
60
60
|
warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'"
|
61
|
-
elsif !File.exist?(File.dirname(file))
|
62
|
-
warn "[skip] directory `#{File.dirname(file)}' does not exist"
|
63
61
|
else
|
62
|
+
dir = File.dirname(file)
|
63
|
+
if !File.exist?(dir)
|
64
|
+
warn "[add] creating `#{dir}'"
|
65
|
+
FileUtils.mkdir_p(dir)
|
66
|
+
end
|
64
67
|
puts "[add] writing `#{file}'"
|
65
68
|
File.open(file, "w") { |f| f.write(content) }
|
66
69
|
end
|
@@ -11,6 +11,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
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,7 +19,7 @@ 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?
|
@@ -38,7 +39,7 @@ Capistrano::Configuration.instance(:must_exist).load do
|
|
38
39
|
args = {
|
39
40
|
:command => fetch(:whenever_command),
|
40
41
|
:flags => fetch(:whenever_clear_flags),
|
41
|
-
:path => fetch(:
|
42
|
+
:path => fetch(:whenever_path)
|
42
43
|
}
|
43
44
|
|
44
45
|
whenever_run_commands(args)
|
@@ -4,7 +4,7 @@ namespace :whenever do
|
|
4
4
|
|
5
5
|
on roles fetch(:whenever_roles) do |host|
|
6
6
|
args_for_host = block_given? ? args + Array(yield(host)) : args
|
7
|
-
within
|
7
|
+
within fetch(:whenever_path) do
|
8
8
|
with fetch(:whenever_command_environment_variables) do
|
9
9
|
execute(*args_for_host)
|
10
10
|
end
|
@@ -12,17 +12,28 @@ namespace :whenever do
|
|
12
12
|
end
|
13
13
|
end
|
14
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
|
+
|
15
24
|
desc "Update application's crontab entries using Whenever"
|
16
25
|
task :update_crontab do
|
17
26
|
setup_whenever_task do |host|
|
18
27
|
roles = host.roles_array.join(",")
|
19
|
-
[fetch(:whenever_update_flags),
|
28
|
+
[fetch(:whenever_update_flags), "--roles=#{roles}", load_file]
|
20
29
|
end
|
21
30
|
end
|
22
31
|
|
23
32
|
desc "Clear application's crontab entries using Whenever"
|
24
33
|
task :clear_crontab do
|
25
|
-
setup_whenever_task
|
34
|
+
setup_whenever_task do |host|
|
35
|
+
[fetch(:whenever_clear_flags), load_file]
|
36
|
+
end
|
26
37
|
end
|
27
38
|
|
28
39
|
after "deploy:updated", "whenever:update_crontab"
|
@@ -33,11 +44,13 @@ namespace :load do
|
|
33
44
|
task :defaults do
|
34
45
|
set :whenever_roles, ->{ :db }
|
35
46
|
set :whenever_command, ->{ [:bundle, :exec, :whenever] }
|
36
|
-
set :whenever_command_environment_variables, ->{
|
47
|
+
set :whenever_command_environment_variables, ->{ fetch(:default_env).merge!(rails_env: fetch(:whenever_environment)) }
|
37
48
|
set :whenever_identifier, ->{ fetch :application }
|
38
49
|
set :whenever_environment, ->{ fetch :rails_env, fetch(:stage, "production") }
|
39
50
|
set :whenever_variables, ->{ "environment=#{fetch :whenever_environment}" }
|
51
|
+
set :whenever_load_file, ->{ nil }
|
40
52
|
set :whenever_update_flags, ->{ "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables}" }
|
41
53
|
set :whenever_clear_flags, ->{ "--clear-crontab #{fetch :whenever_identifier}" }
|
54
|
+
set :whenever_path, ->{ fetch :release_path }
|
42
55
|
end
|
43
56
|
end
|
@@ -9,9 +9,10 @@ module Whenever
|
|
9
9
|
def initialize(options={})
|
10
10
|
@options = options
|
11
11
|
|
12
|
-
@options[:
|
13
|
-
@options[:
|
14
|
-
@options[:
|
12
|
+
@options[:crontab_command] ||= 'crontab'
|
13
|
+
@options[:file] ||= 'config/schedule.rb'
|
14
|
+
@options[:cut] ||= 0
|
15
|
+
@options[:identifier] ||= default_identifier
|
15
16
|
|
16
17
|
if !File.exist?(@options[:file]) && @options[:clear].nil?
|
17
18
|
warn("[fail] Can't find file: #{@options[:file]}")
|
@@ -28,6 +29,8 @@ module Whenever
|
|
28
29
|
exit(1)
|
29
30
|
end
|
30
31
|
@options[:cut] = @options[:cut].to_i
|
32
|
+
|
33
|
+
@timestamp = Time.now.to_s
|
31
34
|
end
|
32
35
|
|
33
36
|
def run
|
@@ -55,9 +58,10 @@ module Whenever
|
|
55
58
|
end
|
56
59
|
|
57
60
|
def read_crontab
|
58
|
-
return @current_crontab if
|
61
|
+
return @current_crontab if instance_variable_defined?(:@current_crontab)
|
59
62
|
|
60
|
-
command = [
|
63
|
+
command = [@options[:crontab_command]]
|
64
|
+
command << '-l'
|
61
65
|
command << "-u #{@options[:user]}" if @options[:user]
|
62
66
|
|
63
67
|
command_results = %x[#{command.join(' ')} 2> /dev/null]
|
@@ -65,7 +69,7 @@ module Whenever
|
|
65
69
|
end
|
66
70
|
|
67
71
|
def write_crontab(contents)
|
68
|
-
command = [
|
72
|
+
command = [@options[:crontab_command]]
|
69
73
|
command << "-u #{@options[:user]}" if @options[:user]
|
70
74
|
# Solaris/SmartOS cron does not support the - option to read from stdin.
|
71
75
|
command << "-" unless OS.solaris?
|
@@ -90,19 +94,19 @@ module Whenever
|
|
90
94
|
|
91
95
|
def updated_crontab
|
92
96
|
# Check for unopened or unclosed identifier blocks
|
93
|
-
if read_crontab =~ Regexp.new("^#{
|
94
|
-
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)}'"
|
95
99
|
exit(1)
|
96
|
-
elsif (read_crontab =~ Regexp.new("^#{
|
97
|
-
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)}'"
|
98
102
|
exit(1)
|
99
103
|
end
|
100
104
|
|
101
105
|
# If an existing identier block is found, replace it with the new cron entries
|
102
|
-
if read_crontab =~ Regexp.new("^#{
|
106
|
+
if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
|
103
107
|
# If the existing crontab file contains backslashes they get lost going through gsub.
|
104
108
|
# .gsub('\\', '\\\\\\') preserves them. Go figure.
|
105
|
-
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('\\', '\\\\\\'))
|
106
110
|
else # Otherwise, append the new cron entries after any existing ones
|
107
111
|
[read_crontab, whenever_cron].join("\n\n")
|
108
112
|
end.gsub(/\n{3,}/, "\n\n") # More than two newlines becomes just two.
|
@@ -120,16 +124,32 @@ module Whenever
|
|
120
124
|
stripped_contents.gsub!(/\s+$/, $/)
|
121
125
|
end
|
122
126
|
|
123
|
-
def comment_base
|
124
|
-
|
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
|
133
|
+
end
|
134
|
+
|
135
|
+
def comment_open(include_timestamp = true)
|
136
|
+
"# Begin #{comment_base(include_timestamp)}"
|
137
|
+
end
|
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}|)"
|
125
145
|
end
|
126
146
|
|
127
|
-
def
|
128
|
-
"#
|
147
|
+
def comment_close_regex
|
148
|
+
"#{comment_close(false)}(#{timestamp_regex}|)"
|
129
149
|
end
|
130
150
|
|
131
|
-
def
|
132
|
-
"
|
151
|
+
def timestamp_regex
|
152
|
+
" at: \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} ([+-]\\d{4}|UTC)"
|
133
153
|
end
|
134
154
|
end
|
135
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 '|'})|((\*?[\d\/,\-]*)\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
|
@@ -54,18 +58,18 @@ module Whenever
|
|
54
58
|
|
55
59
|
protected
|
56
60
|
def day_given?
|
57
|
-
|
58
|
-
@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/])
|
59
62
|
end
|
60
63
|
|
61
64
|
def parse_symbol
|
62
65
|
shortcut = case @time
|
63
66
|
when *KEYWORDS then "@#{@time}" # :reboot => '@reboot'
|
64
|
-
when :year then Whenever.seconds(
|
67
|
+
when :year then Whenever.seconds(1, :year)
|
65
68
|
when :day then Whenever.seconds(1, :day)
|
66
69
|
when :month then Whenever.seconds(1, :month)
|
67
70
|
when :week then Whenever.seconds(1, :week)
|
68
71
|
when :hour then Whenever.seconds(1, :hour)
|
72
|
+
when :minute then Whenever.seconds(1, :minute)
|
69
73
|
end
|
70
74
|
|
71
75
|
if shortcut.is_a?(Numeric)
|
@@ -94,13 +98,15 @@ module Whenever
|
|
94
98
|
hour_frequency = (@time / 60 / 60).round
|
95
99
|
timing[0] = @at.is_a?(Time) ? @at.min : @at
|
96
100
|
timing[1] = comma_separated_timing(hour_frequency, 23)
|
101
|
+
raise ArgumentError, "Minute must be between 0-59, #{timing[0]} given" unless (0..59).include?(timing[0])
|
97
102
|
when Whenever.seconds(1, :day)...Whenever.seconds(1, :month)
|
98
103
|
day_frequency = (@time / 24 / 60 / 60).round
|
99
104
|
timing[0] = @at.is_a?(Time) ? @at.min : 0
|
100
105
|
timing[1] = @at.is_a?(Time) ? @at.hour : @at
|
101
106
|
timing[2] = comma_separated_timing(day_frequency, 31, 1)
|
102
|
-
|
103
|
-
|
107
|
+
raise ArgumentError, "Hour must be between 0-23, #{timing[1]} given" unless (0..23).include?(timing[1])
|
108
|
+
when Whenever.seconds(1, :month)...Whenever.seconds(1, :year)
|
109
|
+
month_frequency = (@time / 30 / 24 / 60 / 60).round
|
104
110
|
timing[0] = @at.is_a?(Time) ? @at.min : 0
|
105
111
|
timing[1] = @at.is_a?(Time) ? @at.hour : 0
|
106
112
|
timing[2] = if @at.is_a?(Time)
|
@@ -109,6 +115,21 @@ module Whenever
|
|
109
115
|
@at.zero? ? 1 : @at
|
110
116
|
end
|
111
117
|
timing[3] = comma_separated_timing(month_frequency, 12, 1)
|
118
|
+
raise ArgumentError, "Day must be between 1-31, #{timing[2]} given" unless (1..31).include?(timing[2])
|
119
|
+
when Whenever.seconds(1, :year)
|
120
|
+
timing[0] = @at.is_a?(Time) ? @at.min : 0
|
121
|
+
timing[1] = @at.is_a?(Time) ? @at.hour : 0
|
122
|
+
timing[2] = if @at.is_a?(Time)
|
123
|
+
day_given? ? @at.day : 1
|
124
|
+
else
|
125
|
+
1
|
126
|
+
end
|
127
|
+
timing[3] = if @at.is_a?(Time)
|
128
|
+
day_given? ? @at.month : 1
|
129
|
+
else
|
130
|
+
@at.zero? ? 1 : @at
|
131
|
+
end
|
132
|
+
raise ArgumentError, "Month must be between 1-12, #{timing[3]} given" unless (1..12).include?(timing[3])
|
112
133
|
else
|
113
134
|
return parse_as_string
|
114
135
|
end
|
@@ -126,7 +147,7 @@ module Whenever
|
|
126
147
|
return (timing << '1-5') * " " if string.downcase.index('weekday')
|
127
148
|
return (timing << '6,0') * " " if string.downcase.index('weekend')
|
128
149
|
|
129
|
-
|
150
|
+
DAYS.each_with_index do |day, i|
|
130
151
|
return (timing << i) * " " if string.downcase.index(day)
|
131
152
|
end
|
132
153
|
|