javan-whenever 0.1.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.
- data/README.rdoc +79 -0
- data/Rakefile +14 -0
- data/bin/whenever +15 -0
- data/bin/wheneverize +67 -0
- data/lib/base.rb +12 -0
- data/lib/job_list.rb +84 -0
- data/lib/job_types/default.rb +17 -0
- data/lib/job_types/runner.rb +30 -0
- data/lib/outputs/cron.rb +104 -0
- data/lib/tasks/whenever.rake +21 -0
- data/lib/whenever.rb +24 -0
- data/test/cron_test.rb +187 -0
- data/test/output_command_test.rb +70 -0
- data/test/output_env_test.rb +23 -0
- data/test/output_runner_test.rb +75 -0
- data/test/test_helper.rb +35 -0
- data/whenever.gemspec +39 -0
- metadata +108 -0
data/README.rdoc
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
= Whenever
|
2
|
+
|
3
|
+
Whenever is a ruby gem that provides a ruby syntax for defining cron jobs. It was designed to work well with Rails applications, but can be used independently as well.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
To install Whenever in a Rails (2.1 or greater) application:
|
8
|
+
|
9
|
+
in your "config/environment.rb" file:
|
10
|
+
|
11
|
+
Rails::Initializer.run do |config|
|
12
|
+
config.gem 'javan-whenever', :lib => 'whenever', :source => 'http://gems.github.com'
|
13
|
+
end
|
14
|
+
|
15
|
+
To install this gem (and all other missing gem dependencies), run rake gems:install (use sudo if necessary).
|
16
|
+
|
17
|
+
In older versions of Rails:
|
18
|
+
|
19
|
+
$ gem sources -a http://gems.github.com
|
20
|
+
$ gem install whenever
|
21
|
+
|
22
|
+
in your "config/environment.rb" file:
|
23
|
+
|
24
|
+
Rails::Initializer.run do |config|
|
25
|
+
...
|
26
|
+
end
|
27
|
+
|
28
|
+
require "whenever"
|
29
|
+
|
30
|
+
== Getting started
|
31
|
+
|
32
|
+
$ cd /my/rails/app
|
33
|
+
$ wheneverize .
|
34
|
+
|
35
|
+
This will create an initial "config/schedule.rb" file you.
|
36
|
+
|
37
|
+
== Example schedule.rb file
|
38
|
+
|
39
|
+
set :runner_path, '/var/www/apps/my_app' # Whenever will try to use your RAILS_ROOT if this isn't set
|
40
|
+
set :runner_environment, :production # Whenever defaults to production so only set this if you want to use a different environment.
|
41
|
+
set :cron_log, '/path/to/my/cronlog.log' # Where to log (this should NOT be your Rails log)
|
42
|
+
|
43
|
+
|
44
|
+
every 2.hours do
|
45
|
+
runner "MyModel.some_process" # runners are the script/runners you know and love
|
46
|
+
command "/usr/local/bin/my_great_command" # commands are any unix command
|
47
|
+
end
|
48
|
+
|
49
|
+
every 1.day, :at => '4:30 am' do # If not :at option is set these jobs will run at midnight
|
50
|
+
runner "DB.Backup", :cron_log => false # You can specify false for no logging or a string a different log file to override logging.
|
51
|
+
end
|
52
|
+
|
53
|
+
every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
|
54
|
+
runner "SomeModel.ladeda"
|
55
|
+
end
|
56
|
+
|
57
|
+
every :sunday do # Use any day of the week or :weekend, :weekday
|
58
|
+
runner "Task.do_something_great"
|
59
|
+
end
|
60
|
+
|
61
|
+
== Cron output
|
62
|
+
|
63
|
+
$ cd /my/rails/app
|
64
|
+
$ whenever
|
65
|
+
|
66
|
+
And you'll see your schedule.rb converted to cron sytax
|
67
|
+
|
68
|
+
== Capistrano integration
|
69
|
+
|
70
|
+
Use the "whenever:write_cron" task to automatically write your crontab file with each deploy.
|
71
|
+
|
72
|
+
in your "config/deploy.rb" file do something like:
|
73
|
+
|
74
|
+
after "deploy:symlink", "whenever:write_cron"
|
75
|
+
|
76
|
+
THIS WILL COMPLETELY OVERWRITE ANY EXISTING CRONTAB ENTRIES!
|
77
|
+
------------------------------------------------------------
|
78
|
+
|
79
|
+
Better documentation on the way!
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
require 'lib/base'
|
5
|
+
|
6
|
+
Echoe.new('whenever', Whenever::VERSION) do |p|
|
7
|
+
p.description = "Provides (clean) ruby syntax for defining (messy) cron jobs and running them Whenever."
|
8
|
+
p.url = "http://github.com/javan/whenever"
|
9
|
+
p.author = "Javan Makhmali"
|
10
|
+
p.email = "javan@javan.us"
|
11
|
+
p.dependencies = ["chronic", "activesupport"]
|
12
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
13
|
+
p.development_dependencies = []
|
14
|
+
end
|
data/bin/whenever
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'optparse'
|
5
|
+
require 'whenever'
|
6
|
+
|
7
|
+
task = "whenever:output_cron"
|
8
|
+
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: whenever [options]"
|
11
|
+
opts.on('-c', '--write-crontab') { task = "whenever:write_cron" }
|
12
|
+
opts.on('-v', '--version') { puts "Whenever v#{Whenever::VERSION}"; exit }
|
13
|
+
end.parse!
|
14
|
+
|
15
|
+
Rake::Task[task].invoke
|
data/bin/wheneverize
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This file is based heavily on Capistrano's `capify` command
|
4
|
+
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: #{File.basename($0)} [path]"
|
9
|
+
|
10
|
+
begin
|
11
|
+
opts.parse!(ARGV)
|
12
|
+
rescue OptionParser::ParseError => e
|
13
|
+
warn e.message
|
14
|
+
puts opts
|
15
|
+
exit 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if ARGV.empty?
|
20
|
+
abort "Please specify the directory to wheneverize, e.g. `#{File.basename($0)} .'"
|
21
|
+
elsif !File.exists?(ARGV.first)
|
22
|
+
abort "`#{ARGV.first}' does not exist."
|
23
|
+
elsif !File.directory?(ARGV.first)
|
24
|
+
abort "`#{ARGV.first}' is not a directory."
|
25
|
+
elsif ARGV.length > 1
|
26
|
+
abort "Too many arguments; please specify only the directory to wheneverize."
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
content = <<-FILE
|
31
|
+
# Use this file to easily define all of your cron jobs.
|
32
|
+
#
|
33
|
+
# It's helpful, but not entirely necessary to understand cron before proceeding.
|
34
|
+
# http://en.wikipedia.org/wiki/Cron
|
35
|
+
|
36
|
+
# Example:
|
37
|
+
#
|
38
|
+
# set :cron_log, "/path/to/my/cron_log.log"
|
39
|
+
#
|
40
|
+
# every 2.hours do
|
41
|
+
# command "/usr/bin/some_great_command"
|
42
|
+
# runner "MyModel.some_method"
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# every 4.days do
|
46
|
+
# runner "AnotherModel.prune_old_records"
|
47
|
+
# end
|
48
|
+
|
49
|
+
# Learn more: http://github.com/javan/whenever
|
50
|
+
FILE
|
51
|
+
|
52
|
+
file = 'config/schedule.rb'
|
53
|
+
base = ARGV.shift
|
54
|
+
|
55
|
+
file = File.join(base, file)
|
56
|
+
if File.exists?(file)
|
57
|
+
warn "[skip] `#{file}' already exists"
|
58
|
+
elsif File.exists?(file.downcase)
|
59
|
+
warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'"
|
60
|
+
elsif !File.exists?(File.dirname(file))
|
61
|
+
warn "[skip] directory `#{File.dirname(file)}' does not exist"
|
62
|
+
else
|
63
|
+
puts "[add] writing `#{file}'"
|
64
|
+
File.open(file, "w") { |f| f.write(content) }
|
65
|
+
end
|
66
|
+
|
67
|
+
puts "[done] wheneverized!"
|
data/lib/base.rb
ADDED
data/lib/job_list.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module Whenever
|
2
|
+
class JobList
|
3
|
+
|
4
|
+
def initialize(options)
|
5
|
+
@jobs = Hash.new
|
6
|
+
@env = Hash.new
|
7
|
+
|
8
|
+
config = case options
|
9
|
+
when String then options
|
10
|
+
when Hash
|
11
|
+
if options[:string]
|
12
|
+
options[:string]
|
13
|
+
elsif options[:file]
|
14
|
+
File.read(options[:file])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
eval(config)
|
19
|
+
end
|
20
|
+
|
21
|
+
def set(variable, value)
|
22
|
+
instance_variable_set("@#{variable}".to_sym, value)
|
23
|
+
self.class.send(:attr_reader, variable.to_sym)
|
24
|
+
end
|
25
|
+
|
26
|
+
def env(variable, value)
|
27
|
+
@env[variable.to_s] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def every(frequency, options = {})
|
31
|
+
@current_time_scope = frequency
|
32
|
+
@options = options
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
|
36
|
+
def command(task, options = {})
|
37
|
+
options[:cron_log] ||= @cron_log unless options[:cron_log] === false
|
38
|
+
options[:class] ||= Whenever::Job::Default
|
39
|
+
@jobs[@current_time_scope] ||= []
|
40
|
+
@jobs[@current_time_scope] << options[:class].new(@options.merge(:task => task).merge(options))
|
41
|
+
end
|
42
|
+
|
43
|
+
def runner(task, options = {})
|
44
|
+
options.reverse_merge!(:environment => @runner_environment, :path => @runner_path)
|
45
|
+
options[:class] = Whenever::Job::Runner
|
46
|
+
command(task, options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_cron_output
|
50
|
+
[environment_variables, cron_jobs].compact.join
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def environment_variables
|
56
|
+
return if @env.empty?
|
57
|
+
|
58
|
+
output = []
|
59
|
+
@env.each do |key, val|
|
60
|
+
output << "#{key}=#{val}\n"
|
61
|
+
end
|
62
|
+
output << "\n"
|
63
|
+
|
64
|
+
output.join
|
65
|
+
end
|
66
|
+
|
67
|
+
def cron_jobs
|
68
|
+
return if @jobs.empty?
|
69
|
+
|
70
|
+
output = []
|
71
|
+
@jobs.each do |time, jobs|
|
72
|
+
jobs.each do |job|
|
73
|
+
cron = Whenever::Output::Cron.output(time, job)
|
74
|
+
cron << " >> #{job.cron_log} 2>&1" if job.cron_log
|
75
|
+
cron << "\n\n"
|
76
|
+
output << cron
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
output.join
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Whenever
|
2
|
+
module Job
|
3
|
+
class Default
|
4
|
+
attr_accessor :task, :at, :cron_log
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@task = options[:task]
|
8
|
+
@at = options[:at]
|
9
|
+
@cron_log = options[:cron_log]
|
10
|
+
end
|
11
|
+
|
12
|
+
def output
|
13
|
+
task
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Whenever
|
2
|
+
module Job
|
3
|
+
class Runner < Whenever::Job::Default
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
super(options)
|
7
|
+
|
8
|
+
@environment = options[:environment] || :production
|
9
|
+
|
10
|
+
if [Whenever::Job::Runner.rails_root, options[:path]].all?(&:blank?)
|
11
|
+
raise ArgumentError, "no cron_path available for runner to use"
|
12
|
+
else
|
13
|
+
@path = options[:path] || Whenever::Job::Runner.rails_root
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.rails_root
|
18
|
+
if defined?(RAILS_ROOT)
|
19
|
+
RAILS_ROOT
|
20
|
+
elsif defined?(::RAILS_ROOT)
|
21
|
+
::RAILS_ROOT
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def output
|
26
|
+
%Q(#{File.join(@path, 'script', 'runner')} -e #{@environment} "#{task}")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/outputs/cron.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module Whenever
|
2
|
+
module Output
|
3
|
+
|
4
|
+
class Cron
|
5
|
+
|
6
|
+
attr_accessor :time, :task
|
7
|
+
|
8
|
+
def initialize(time = nil, task = nil, at = nil)
|
9
|
+
@time = time
|
10
|
+
@task = task
|
11
|
+
@at = at.is_a?(String) ? (Chronic.parse(at) || 0) : (at || 0)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.output(time, job)
|
15
|
+
out = new(time, job.output, job.at)
|
16
|
+
"#{out.time_in_cron_syntax} #{out.task}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def time_in_cron_syntax
|
20
|
+
case @time
|
21
|
+
when Symbol then parse_symbol
|
22
|
+
when String then parse_as_string
|
23
|
+
else parse_time
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def parse_symbol
|
30
|
+
case @time
|
31
|
+
when :reboot then '@reboot'
|
32
|
+
when :year, :yearly then '@annually'
|
33
|
+
when :day, :daily then '@daily'
|
34
|
+
when :midnight then '@midnight'
|
35
|
+
when :month, :monthly then '@monthly'
|
36
|
+
when :week, :weekly then '@weekly'
|
37
|
+
when :hour, :hourly then '@hourly'
|
38
|
+
else parse_as_string
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_time
|
43
|
+
timing = Array.new(5, '*')
|
44
|
+
case @time
|
45
|
+
when 0.seconds...1.minute
|
46
|
+
raise ArgumentError, "Time must be in minutes or higher"
|
47
|
+
when 1.minute...1.hour
|
48
|
+
minute_frequency = @time / 60
|
49
|
+
timing[0] = comma_separated_timing(minute_frequency, 59)
|
50
|
+
when 1.hour...1.day
|
51
|
+
hour_frequency = (@time / 60 / 60).round
|
52
|
+
timing[0] = @at.is_a?(Time) ? @at.min : @at
|
53
|
+
timing[1] = comma_separated_timing(hour_frequency, 23)
|
54
|
+
when 1.day...1.month
|
55
|
+
day_frequency = (@time / 24 / 60 / 60).round
|
56
|
+
timing[0] = @at.is_a?(Time) ? @at.min : 0
|
57
|
+
timing[1] = @at.is_a?(Time) ? @at.hour : @at
|
58
|
+
timing[2] = comma_separated_timing(day_frequency, 31, 1)
|
59
|
+
when 1.month..12.months
|
60
|
+
month_frequency = (@time / 30 / 24 / 60 / 60).round
|
61
|
+
timing[0] = @at.is_a?(Time) ? @at.min : 0
|
62
|
+
timing[1] = @at.is_a?(Time) ? @at.hour : 0
|
63
|
+
timing[2] = @at.is_a?(Time) ? @at.day : (@at.zero? ? 1 : @at)
|
64
|
+
timing[3] = comma_separated_timing(month_frequency, 12, 1)
|
65
|
+
else
|
66
|
+
return parse_as_string
|
67
|
+
end
|
68
|
+
timing.join(' ')
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_as_string
|
72
|
+
return unless @time
|
73
|
+
string = @time.to_s
|
74
|
+
|
75
|
+
return "0 0 * * mon-fri" if string.downcase.index('weekday')
|
76
|
+
return "0 0 * * sat,sun" if string.downcase.index('weekend')
|
77
|
+
|
78
|
+
%w(sun mon tue wed thu fri sat).each do |day|
|
79
|
+
return "0 0 * * #{day}" if string.downcase.index(day)
|
80
|
+
end
|
81
|
+
|
82
|
+
raise ArgumentError, "Couldn't parse: #{@time}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def comma_separated_timing(frequency, max, start = 0)
|
86
|
+
return start if frequency.blank? || frequency.zero?
|
87
|
+
return '*' if frequency == 1
|
88
|
+
return frequency if frequency > (max * 0.5).ceil
|
89
|
+
|
90
|
+
original_start = start
|
91
|
+
|
92
|
+
start += frequency unless (max + 1).modulo(frequency).zero? || start > 0
|
93
|
+
output = (start..max).step(frequency).to_a
|
94
|
+
|
95
|
+
max_occurances = (max.to_f / (frequency.to_f)).round
|
96
|
+
max_occurances += 1 if original_start.zero?
|
97
|
+
|
98
|
+
output[0, max_occurances].join(',')
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
namespace :whenever do
|
2
|
+
|
3
|
+
desc "outputs cron"
|
4
|
+
task :output_cron do
|
5
|
+
puts Whenever.cron(:file => "config/schedule.rb")
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "writes cron"
|
9
|
+
task :write_cron do
|
10
|
+
require 'tempfile'
|
11
|
+
cron_output = Whenever.cron(:file => "config/schedule.rb")
|
12
|
+
|
13
|
+
tmp_cron_file = Tempfile.new('whenever_tmp_cron').path
|
14
|
+
File.open(tmp_cron_file, File::WRONLY | File::APPEND) do |file|
|
15
|
+
file << cron_output
|
16
|
+
end
|
17
|
+
sh "crontab #{tmp_cron_file}"
|
18
|
+
puts "[write] crontab file updated"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/lib/whenever.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
unless defined?(Whenever)
|
2
|
+
$:.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
# Hoping to load Rails' Rakefile
|
5
|
+
begin
|
6
|
+
load 'Rakefile'
|
7
|
+
rescue LoadError => e
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
# Load Whenever's rake tasks
|
12
|
+
begin
|
13
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks', '*.rake')].each { |rake| load rake }
|
14
|
+
rescue LoadError => e
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
require 'activesupport'
|
22
|
+
require 'chronic'
|
23
|
+
|
24
|
+
require 'base'
|
data/test/cron_test.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CronTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "When parsing time in minutes" do
|
6
|
+
should "raise if less than 1 minute" do
|
7
|
+
assert_raises ArgumentError do
|
8
|
+
parse_time(59.seconds)
|
9
|
+
end
|
10
|
+
|
11
|
+
assert_raises ArgumentError do
|
12
|
+
parse_time(0.minutes)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# For santity, do some tests on straight String
|
17
|
+
should "parse correctly" do
|
18
|
+
assert_equal '* * * * *', parse_time(1.minute)
|
19
|
+
assert_equal '0,5,10,15,20,25,30,35,40,45,50,55 * * * *', parse_time(5.minutes)
|
20
|
+
assert_equal '7,14,21,28,35,42,49,56 * * * *', parse_time(7.minutes)
|
21
|
+
assert_equal '0,30 * * * *', parse_time(30.minutes)
|
22
|
+
assert_equal '32 * * * *', parse_time(32.minutes)
|
23
|
+
assert_not_equal '60 * * * *', parse_time(60.minutes) # 60 minutes bumps up into the hour range
|
24
|
+
end
|
25
|
+
|
26
|
+
# Test all minutes
|
27
|
+
(2..59).each do |num|
|
28
|
+
should "parse correctly for #{num} minutes" do
|
29
|
+
start = 0
|
30
|
+
start += num unless 60.modulo(num).zero?
|
31
|
+
minutes = (start..59).step(num).to_a
|
32
|
+
|
33
|
+
assert_equal "#{minutes.join(',')} * * * *", parse_time(num.minutes)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "When parsing time in hours" do
|
39
|
+
should "parse correctly" do
|
40
|
+
assert_equal '0 * * * *', parse_time(1.hour)
|
41
|
+
assert_equal '0 0,2,4,6,8,10,12,14,16,18,20,22 * * *', parse_time(2.hours)
|
42
|
+
assert_equal '0 0,3,6,9,12,15,18,21 * * *', parse_time(3.hours)
|
43
|
+
assert_equal '0 5,10,15,20 * * *', parse_time(5.hours)
|
44
|
+
assert_equal '0 17 * * *', parse_time(17.hours)
|
45
|
+
assert_not_equal '0 24 * * *', parse_time(24.hours) # 24 hours bumps up into the day range
|
46
|
+
end
|
47
|
+
|
48
|
+
(2..23).each do |num|
|
49
|
+
should "parse correctly for #{num} hours" do
|
50
|
+
start = 0
|
51
|
+
start += num unless 24.modulo(num).zero?
|
52
|
+
hours = (start..23).step(num).to_a
|
53
|
+
|
54
|
+
assert_equal "0 #{hours.join(',')} * * *", parse_time(num.hours)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
should "parse correctly when given an 'at' with minutes as an Integer" do
|
59
|
+
assert_minutes_equals "1", 1
|
60
|
+
assert_minutes_equals "14", 14
|
61
|
+
assert_minutes_equals "27", 27
|
62
|
+
assert_minutes_equals "55", 55
|
63
|
+
end
|
64
|
+
|
65
|
+
should "parse correctly when given an 'at' with minutes as a Time" do
|
66
|
+
# Basically just testing that Chronic parses some times and we get the minutes out of it
|
67
|
+
assert_minutes_equals "1", '3:01am'
|
68
|
+
assert_minutes_equals "1", 'January 21 2:01 PM'
|
69
|
+
assert_minutes_equals "0", 'midnight'
|
70
|
+
assert_minutes_equals "59", '13:59'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "When parsing time in days (of month)" do
|
75
|
+
should "parse correctly" do
|
76
|
+
assert_equal '0 0 * * *', parse_time(1.days)
|
77
|
+
assert_equal '0 0 1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31 * *', parse_time(2.days)
|
78
|
+
assert_equal '0 0 1,5,9,13,17,21,25,29 * *', parse_time(4.days)
|
79
|
+
assert_equal '0 0 1,8,15,22 * *', parse_time(7.days)
|
80
|
+
assert_equal '0 0 1,17 * *', parse_time(16.days)
|
81
|
+
assert_equal '0 0 17 * *', parse_time(17.days)
|
82
|
+
assert_equal '0 0 29 * *', parse_time(29.days)
|
83
|
+
assert_not_equal '0 0 30 * *', parse_time(30.days) # 30 days bumps into the month range
|
84
|
+
end
|
85
|
+
|
86
|
+
should "parse correctly when given an 'at' with hours, minutes as a Time" do
|
87
|
+
# first param is an array with [hours, minutes]
|
88
|
+
assert_hours_and_minutes_equals %w(3 45), '3:45am'
|
89
|
+
assert_hours_and_minutes_equals %w(20 1), '8:01pm'
|
90
|
+
assert_hours_and_minutes_equals %w(0 0), 'midnight'
|
91
|
+
assert_hours_and_minutes_equals %w(1 23), '1:23 AM'
|
92
|
+
assert_hours_and_minutes_equals %w(23 59), 'March 21 11:59 pM'
|
93
|
+
end
|
94
|
+
|
95
|
+
should "parse correctly when given an 'at' with hours as an Integer" do
|
96
|
+
# first param is an array with [hours, minutes]
|
97
|
+
assert_hours_and_minutes_equals %w(1 0), 1
|
98
|
+
assert_hours_and_minutes_equals %w(3 0), 3
|
99
|
+
assert_hours_and_minutes_equals %w(15 0), 15
|
100
|
+
assert_hours_and_minutes_equals %w(19 0), 19
|
101
|
+
assert_hours_and_minutes_equals %w(23 0), 23
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "When parsing time in months" do
|
106
|
+
should "parse correctly" do
|
107
|
+
assert_equal '0 0 1 * *', parse_time(1.month)
|
108
|
+
assert_equal '0 0 1 1,3,5,7,9,11 *', parse_time(2.months)
|
109
|
+
assert_equal '0 0 1 1,4,7,10 *', parse_time(3.months)
|
110
|
+
assert_equal '0 0 1 1,5,9 *', parse_time(4.months)
|
111
|
+
assert_equal '0 0 1 1,6 *', parse_time(5.months)
|
112
|
+
assert_equal '0 0 1 7 *', parse_time(7.months)
|
113
|
+
assert_equal '0 0 1 8 *', parse_time(8.months)
|
114
|
+
assert_equal '0 0 1 9 *', parse_time(9.months)
|
115
|
+
assert_equal '0 0 1 10 *', parse_time(10.months)
|
116
|
+
assert_equal '0 0 1 11 *', parse_time(11.months)
|
117
|
+
assert_equal '0 0 1 12 *', parse_time(12.months)
|
118
|
+
end
|
119
|
+
|
120
|
+
should "parse correctly when given an 'at' with days, hours, minutes as a Time" do
|
121
|
+
# first param is an array with [days, hours, minutes]
|
122
|
+
assert_days_and_hours_and_minutes_equals %w(1 3 45), 'January 1st 3:45am'
|
123
|
+
assert_days_and_hours_and_minutes_equals %w(11 23 0), 'Feb 11 11PM'
|
124
|
+
assert_days_and_hours_and_minutes_equals %w(22 1 1), 'march 22nd at 1:01 am'
|
125
|
+
assert_days_and_hours_and_minutes_equals %w(23 0 0), 'march 22nd at midnight' # looks like midnight means the next day
|
126
|
+
end
|
127
|
+
|
128
|
+
should "parse correctly when given an 'at' with days as an Integer" do
|
129
|
+
# first param is an array with [days, hours, minutes]
|
130
|
+
assert_days_and_hours_and_minutes_equals %w(1 0 0), 1
|
131
|
+
assert_days_and_hours_and_minutes_equals %w(15 0 0), 15
|
132
|
+
assert_days_and_hours_and_minutes_equals %w(29 0 0), 29
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context "When parsing time in days (of week)" do
|
137
|
+
should "parse days of the week correctly" do
|
138
|
+
{
|
139
|
+
'sun' => %w(sun Sunday SUNDAY SUN),
|
140
|
+
'mon' => %w(mon Monday MONDAY MON),
|
141
|
+
'tue' => %w(tue tues Tuesday TUESDAY TUE),
|
142
|
+
'wed' => %w(wed Wednesday WEDNESDAY WED),
|
143
|
+
'thu' => %w(thu thurs thur Thursday THURSDAY THU),
|
144
|
+
'fri' => %w(fri Friday FRIDAY FRI),
|
145
|
+
'sat' => %w(sat Saturday SATURDAY SAT)
|
146
|
+
}.each do |day, day_tests|
|
147
|
+
day_tests.each do |day_test|
|
148
|
+
assert_equal "0 0 * * #{day}", parse_time(day_test)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
should "parse weekday correctly" do
|
154
|
+
assert_equal '0 0 * * mon-fri', parse_time('weekday')
|
155
|
+
assert_equal '0 0 * * mon-fri', parse_time('Weekdays')
|
156
|
+
end
|
157
|
+
|
158
|
+
should "parse weekend correctly" do
|
159
|
+
assert_equal '0 0 * * sat,sun', parse_time('weekend')
|
160
|
+
assert_equal '0 0 * * sat,sun', parse_time('Weekends')
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def assert_days_and_hours_and_minutes_equals(expected, time)
|
167
|
+
cron = parse_time(2.months, 'some task', time)
|
168
|
+
minutes, hours, days, *garbage = cron.split(' ')
|
169
|
+
assert_equal expected, [days, hours, minutes]
|
170
|
+
end
|
171
|
+
|
172
|
+
def assert_hours_and_minutes_equals(expected, time)
|
173
|
+
cron = parse_time(2.days, 'some task', time)
|
174
|
+
minutes, hours, *garbage = cron.split(' ')
|
175
|
+
assert_equal expected, [hours, minutes]
|
176
|
+
end
|
177
|
+
|
178
|
+
def assert_minutes_equals(expected, time)
|
179
|
+
cron = parse_time(2.hours, 'some task', time)
|
180
|
+
assert_equal expected, cron.split(' ')[0]
|
181
|
+
end
|
182
|
+
|
183
|
+
def parse_time(time = nil, task = nil, at = nil)
|
184
|
+
Whenever::Output::Cron.new(time, task, at).time_in_cron_syntax
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class OutputCommandTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "A plain command" do
|
6
|
+
setup do
|
7
|
+
@output = load_whenever_output \
|
8
|
+
<<-file
|
9
|
+
every 2.hours do
|
10
|
+
command "blahblah"
|
11
|
+
end
|
12
|
+
file
|
13
|
+
end
|
14
|
+
|
15
|
+
should "output the command" do
|
16
|
+
assert_match /^.+ .+ .+ .+ blahblah$/, @output
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "A command when the cron_log is set" do
|
21
|
+
setup do
|
22
|
+
@output = load_whenever_output \
|
23
|
+
<<-file
|
24
|
+
set :cron_log, 'logfile.log'
|
25
|
+
every 2.hours do
|
26
|
+
command "blahblah"
|
27
|
+
end
|
28
|
+
file
|
29
|
+
end
|
30
|
+
|
31
|
+
should "output the command with the log syntax appended" do
|
32
|
+
assert_match /^.+ .+ .+ .+ blahblah >> logfile.log 2>&1$/, @output
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "A command when the cron_log is set and the comand overrides it" do
|
37
|
+
setup do
|
38
|
+
@output = load_whenever_output \
|
39
|
+
<<-file
|
40
|
+
set :cron_log, 'logfile.log'
|
41
|
+
every 2.hours do
|
42
|
+
command "blahblah", :cron_log => 'otherlog.log'
|
43
|
+
end
|
44
|
+
file
|
45
|
+
end
|
46
|
+
|
47
|
+
should "output the command with the log syntax appended" do
|
48
|
+
assert_no_match /.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, @output
|
49
|
+
assert_match /^.+ .+ .+ .+ blahblah >> otherlog.log 2>&1$/, @output
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "A command when the cron_log is set and the comand rejects it" do
|
54
|
+
setup do
|
55
|
+
@output = load_whenever_output \
|
56
|
+
<<-file
|
57
|
+
set :cron_log, 'logfile.log'
|
58
|
+
every 2.hours do
|
59
|
+
command "blahblah", :cron_log => false
|
60
|
+
end
|
61
|
+
file
|
62
|
+
end
|
63
|
+
|
64
|
+
should "output the command without the log syntax appended" do
|
65
|
+
assert_no_match /.+ .+ .+ .+ blahblah >> logfile.log 2>&1/, @output
|
66
|
+
assert_match /^.+ .+ .+ .+ blahblah$/, @output
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class OutputEnvTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "The output from Whenever with environment variables set" do
|
6
|
+
setup do
|
7
|
+
@output = load_whenever_output \
|
8
|
+
<<-file
|
9
|
+
env :MYVAR, 'blah'
|
10
|
+
env 'MAILTO', "someone@example.com"
|
11
|
+
file
|
12
|
+
end
|
13
|
+
|
14
|
+
should "output MYVAR environment variable" do
|
15
|
+
assert_match "MYVAR=blah", @output
|
16
|
+
end
|
17
|
+
|
18
|
+
should "output MAILTO environment variable" do
|
19
|
+
assert_match "MAILTO=someone@example.com", @output
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class OutputRunnerTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "A runner with runner_path set" do
|
6
|
+
setup do
|
7
|
+
@output = load_whenever_output \
|
8
|
+
<<-file
|
9
|
+
set :runner_path, '/my/path'
|
10
|
+
every 2.hours do
|
11
|
+
runner "blahblah"
|
12
|
+
end
|
13
|
+
file
|
14
|
+
end
|
15
|
+
|
16
|
+
should "output the runner using that path" do
|
17
|
+
assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "A runner with no runner_path set and RAILS_ROOT defined" do
|
22
|
+
setup do
|
23
|
+
Whenever::Job::Runner.stubs(:rails_root).returns('/my/path')
|
24
|
+
|
25
|
+
@output = load_whenever_output \
|
26
|
+
<<-file
|
27
|
+
every 2.hours do
|
28
|
+
runner "blahblah"
|
29
|
+
end
|
30
|
+
file
|
31
|
+
end
|
32
|
+
|
33
|
+
should "output the runner using that path" do
|
34
|
+
assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "A runner with runner_path set AND RAILS_ROOT defined" do
|
39
|
+
setup do
|
40
|
+
Whenever::Job::Runner.stubs(:rails_root).returns('/my/path')
|
41
|
+
|
42
|
+
@output = load_whenever_output \
|
43
|
+
<<-file
|
44
|
+
set :runner_path, '/my/path'
|
45
|
+
every 2.hours do
|
46
|
+
runner "blahblah"
|
47
|
+
end
|
48
|
+
file
|
49
|
+
end
|
50
|
+
|
51
|
+
should "use the runner_path" do
|
52
|
+
assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output
|
53
|
+
assert_no_match /\/rails\/path/, @output
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "A runner with no runner_path set and no RAILS_ROOT defined" do
|
58
|
+
setup do
|
59
|
+
Whenever::Job::Runner.stubs(:rails_root).returns(nil)
|
60
|
+
|
61
|
+
@input = <<-file
|
62
|
+
every 2.hours do
|
63
|
+
runner "blahblah"
|
64
|
+
end
|
65
|
+
file
|
66
|
+
end
|
67
|
+
|
68
|
+
should "raise an exception" do
|
69
|
+
assert_raises ArgumentError do
|
70
|
+
load_whenever_output(@input)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'shoulda'
|
6
|
+
rescue LoadError
|
7
|
+
warn 'To test Whenever you need the shoulda gem:'
|
8
|
+
warn '$ sudo gem install thoughtbot-shoulda'
|
9
|
+
exit(1)
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'mocha'
|
14
|
+
rescue LoadError
|
15
|
+
warn 'To test Whenever you need the mocha gem:'
|
16
|
+
warn '$ sudo gem install mocha'
|
17
|
+
exit(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'whenever'
|
21
|
+
|
22
|
+
module TestExtensions
|
23
|
+
|
24
|
+
def load_whenever_output(input)
|
25
|
+
Whenever.cron(input)
|
26
|
+
end
|
27
|
+
|
28
|
+
def two_hours
|
29
|
+
"0 0,2,4,6,8,10,12,14,16,18,20,22 * * *"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Test::Unit::TestCase
|
34
|
+
include TestExtensions
|
35
|
+
end
|
data/whenever.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{whenever}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Javan Makhmali"]
|
9
|
+
s.date = %q{2009-02-15}
|
10
|
+
s.description = %q{Provides (clean) ruby syntax for defining (messy) cron jobs and running them Whenever.}
|
11
|
+
s.email = %q{javan@javan.us}
|
12
|
+
s.executables = ["whenever", "wheneverize"]
|
13
|
+
s.extra_rdoc_files = ["bin/whenever", "bin/wheneverize", "lib/base.rb", "lib/job_list.rb", "lib/job_types/default.rb", "lib/job_types/runner.rb", "lib/outputs/cron.rb", "lib/tasks/whenever.rake", "lib/whenever.rb", "README.rdoc"]
|
14
|
+
s.files = ["bin/whenever", "bin/wheneverize", "lib/base.rb", "lib/job_list.rb", "lib/job_types/default.rb", "lib/job_types/runner.rb", "lib/outputs/cron.rb", "lib/tasks/whenever.rake", "lib/whenever.rb", "Manifest", "Rakefile", "README.rdoc", "test/cron_test.rb", "test/output_command_test.rb", "test/output_env_test.rb", "test/output_runner_test.rb", "test/test_helper.rb", "whenever.gemspec"]
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.homepage = %q{http://github.com/javan/whenever}
|
17
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Whenever", "--main", "README.rdoc"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{whenever}
|
20
|
+
s.rubygems_version = %q{1.3.1}
|
21
|
+
s.summary = %q{Provides (clean) ruby syntax for defining (messy) cron jobs and running them Whenever.}
|
22
|
+
s.test_files = ["test/cron_test.rb", "test/output_command_test.rb", "test/output_env_test.rb", "test/output_runner_test.rb", "test/test_helper.rb"]
|
23
|
+
|
24
|
+
if s.respond_to? :specification_version then
|
25
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
26
|
+
s.specification_version = 2
|
27
|
+
|
28
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
29
|
+
s.add_runtime_dependency(%q<chronic>, [">= 0"])
|
30
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<chronic>, [">= 0"])
|
33
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
34
|
+
end
|
35
|
+
else
|
36
|
+
s.add_dependency(%q<chronic>, [">= 0"])
|
37
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: javan-whenever
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Javan Makhmali
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-15 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: chronic
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: Provides (clean) ruby syntax for defining (messy) cron jobs and running them Whenever.
|
36
|
+
email: javan@javan.us
|
37
|
+
executables:
|
38
|
+
- whenever
|
39
|
+
- wheneverize
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- bin/whenever
|
44
|
+
- bin/wheneverize
|
45
|
+
- lib/base.rb
|
46
|
+
- lib/job_list.rb
|
47
|
+
- lib/job_types/default.rb
|
48
|
+
- lib/job_types/runner.rb
|
49
|
+
- lib/outputs/cron.rb
|
50
|
+
- lib/tasks/whenever.rake
|
51
|
+
- lib/whenever.rb
|
52
|
+
- README.rdoc
|
53
|
+
files:
|
54
|
+
- bin/whenever
|
55
|
+
- bin/wheneverize
|
56
|
+
- lib/base.rb
|
57
|
+
- lib/job_list.rb
|
58
|
+
- lib/job_types/default.rb
|
59
|
+
- lib/job_types/runner.rb
|
60
|
+
- lib/outputs/cron.rb
|
61
|
+
- lib/tasks/whenever.rake
|
62
|
+
- lib/whenever.rb
|
63
|
+
- Manifest
|
64
|
+
- Rakefile
|
65
|
+
- README.rdoc
|
66
|
+
- test/cron_test.rb
|
67
|
+
- test/output_command_test.rb
|
68
|
+
- test/output_env_test.rb
|
69
|
+
- test/output_runner_test.rb
|
70
|
+
- test/test_helper.rb
|
71
|
+
- whenever.gemspec
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: http://github.com/javan/whenever
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options:
|
76
|
+
- --line-numbers
|
77
|
+
- --inline-source
|
78
|
+
- --title
|
79
|
+
- Whenever
|
80
|
+
- --main
|
81
|
+
- README.rdoc
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
version:
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "1.2"
|
95
|
+
version:
|
96
|
+
requirements: []
|
97
|
+
|
98
|
+
rubyforge_project: whenever
|
99
|
+
rubygems_version: 1.2.0
|
100
|
+
signing_key:
|
101
|
+
specification_version: 2
|
102
|
+
summary: Provides (clean) ruby syntax for defining (messy) cron jobs and running them Whenever.
|
103
|
+
test_files:
|
104
|
+
- test/cron_test.rb
|
105
|
+
- test/output_command_test.rb
|
106
|
+
- test/output_env_test.rb
|
107
|
+
- test/output_runner_test.rb
|
108
|
+
- test/test_helper.rb
|