andrewtimberlake-whenever 0.1.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ == 0.1.5 / February 19th, 2009
2
+
3
+ * Fixed load path so Whenever's files don't conflict with anything in Rails. Thanks Ryan Koopmans. [Javan Makhmali]
4
+
5
+
6
+ == 0.1.4 / February 17th, 2009
7
+
8
+ * Added --load-file and --user opts to whenever binary. [Javan Makhmali]
9
+
10
+
11
+ == 0.1.3 / February 16th, 2009
12
+
13
+ * Added 'rake' helper for defining scheduled rake tasks. [Javan Makhmali]
14
+
15
+ * Renamed :cron_environment and :cron_path to :enviroment and :path for better (word) compatibility with rake tasks. [Javan Makhmali]
16
+
17
+ * Improved test load paths so tests can be run individually. [Javan Makhmali]
18
+
19
+ * Got rid of already initialized constant warning. [Javan Makhmali]
20
+
21
+ * Requiring specific gem versions: Chronic >=0.2.3 and activesupport >= 1.3.0 [Javan Makhmali]
22
+
23
+
24
+ == 0.1.0 / February 15th, 2009
25
+
26
+ * Initial release [Javan Makhmali]
@@ -0,0 +1,125 @@
1
+ == Introduction
2
+
3
+ Whenever is a Ruby gem that provides a clear syntax for defining cron jobs. It outputs valid cron syntax and can even write your crontab file for you. It is designed to work well with Rails applications and can be deployed with Capistrano. Whenever works fine independently as well.
4
+
5
+ == Installation
6
+
7
+ Regular (non-Rails) install:
8
+
9
+ $ gem sources -a http://gems.github.com #you only need to run this once
10
+ $ sudo gem install javan-whenever
11
+
12
+ In a Rails (2.1 or greater) application:
13
+
14
+ in your "config/environment.rb" file:
15
+
16
+ Rails::Initializer.run do |config|
17
+ config.gem 'javan-whenever', :lib => false, :version => '>= 0.1.5' :source => 'http://gems.github.com'
18
+ end
19
+
20
+ To install this gem (and all other missing gem dependencies), run rake gems:install (use sudo if necessary).
21
+
22
+ In older versions of Rails:
23
+
24
+ $ gem sources -a http://gems.github.com #you only need to run this once
25
+ $ gem install javan-whenever
26
+
27
+ in your "config/environment.rb" file:
28
+
29
+ Rails::Initializer.run do |config|
30
+ ...
31
+ end
32
+
33
+ require 'whenever'
34
+
35
+ NOTE: Requiring the whenever gem inside your Rails application is technically optional. However, if you plan to use something like Capistrano to automatically deploy and write your crontab file, you'll need to have the gem installed on your servers, and requiring it in your app is one way to ensure this.
36
+
37
+ == Getting started
38
+
39
+ $ cd /my/rails/app
40
+ $ wheneverize .
41
+
42
+ This will create an initial "config/schedule.rb" file you.
43
+
44
+ == Example schedule.rb file
45
+
46
+ every 3.hours do
47
+ runner "MyModel.some_process"
48
+ rake "my:rake:task"
49
+ command "/usr/bin/my_great_command"
50
+ end
51
+
52
+ every 1.day, :at => '4:30 am' do
53
+ runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
54
+ end
55
+
56
+ every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
57
+ runner "SomeModel.ladeeda"
58
+ end
59
+
60
+ every :sunday do # Use any day of the week or :weekend, :weekday
61
+ runner "Task.do_something_great"
62
+ end
63
+
64
+ More examples on the wiki: http://wiki.github.com/javan/whenever/instructions-and-examples
65
+
66
+ == Cron output
67
+
68
+ $ cd /my/rails/app
69
+ $ whenever
70
+
71
+ And you'll see your schedule.rb converted to cron sytax
72
+
73
+ == Capistrano integration
74
+
75
+ in your "config/deploy.rb" file do something like:
76
+
77
+ after "deploy:symlink", "deploy:write_crontab"
78
+
79
+ namespace :deploy do
80
+ desc "write the crontab file"
81
+ task :write_crontab, :roles => :app do
82
+ run "cd #{release_path} && whenever --write-crontab"
83
+ end
84
+ end
85
+
86
+ By mixing and matching the "--load-file" and "--user" options with your various :roles in Capistrano it is entirely possible to deploy different crontab schedules under different users to all your various servers. Get creative!
87
+
88
+ USING THE "--write-crontab" OPTION WILL COMPLETELY OVERWRITE ANY EXISTING CRONTAB ENTRIES!
89
+
90
+ == Credit
91
+
92
+ Whenever was created for use at Inkling (http://inklingmarkets.com) where I work. Their take on it: http://blog.inklingmarkets.com/2009/02/whenever-easy-way-to-do-cron-jobs-from.html
93
+
94
+ While building Whenever, I learned a lot by digging through the source code of Capistrano - http://github.com/jamis/capistrano
95
+
96
+ == Feedback
97
+
98
+ Lighthouse: http://javan.lighthouseapp.com/projects/25781-whenever/overview
99
+
100
+ Email me: javan [at] javan (dot) us
101
+
102
+ == License
103
+
104
+ Copyright (c) 2009 Javan Makhmali
105
+
106
+ Permission is hereby granted, free of charge, to any person
107
+ obtaining a copy of this software and associated documentation
108
+ files (the "Software"), to deal in the Software without
109
+ restriction, including without limitation the rights to use,
110
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
111
+ copies of the Software, and to permit persons to whom the
112
+ Software is furnished to do so, subject to the following
113
+ conditions:
114
+
115
+ The above copyright notice and this permission notice shall be
116
+ included in all copies or substantial portions of the Software.
117
+
118
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
119
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
120
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
121
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
122
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
123
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
124
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
125
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ require File.expand_path(File.dirname(__FILE__) + "/lib/version")
6
+
7
+ Echoe.new('whenever', Whenever::VERSION::STRING) do |p|
8
+ p.description = "Provides clean ruby syntax for defining messy cron jobs and running them Whenever."
9
+ p.url = "http://github.com/javan/whenever"
10
+ p.author = "Javan Makhmali"
11
+ p.email = "javan@javan.us"
12
+ p.dependencies = ["chronic >=0.2.3", "activesupport >=1.3.0"]
13
+ end
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << "/home/andrew/dev/whenever/lib"
4
+
5
+ require 'rubygems'
6
+ require 'optparse'
7
+ require 'fileutils'
8
+ require 'tempfile'
9
+ require 'whenever'
10
+
11
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/version")
12
+
13
+ options = Hash.new
14
+
15
+ OptionParser.new do |opts|
16
+ opts.banner = "Usage: whenever [options]"
17
+ opts.on('-v', '--version') { puts "Whenever v#{Whenever::VERSION::STRING}"; exit }
18
+ opts.on('-w', '--write-crontab') { options[:write] = true }
19
+ opts.on('-f', '--load-file [schedule file]', 'default: config/schedule.rb') do |file|
20
+ options[:file] = file if file
21
+ end
22
+ opts.on('-u', '--user [user]', 'default: current user') do |user|
23
+ options[:user] = user if user
24
+ end
25
+ end.parse!
26
+
27
+ options[:file] ||= 'config/schedule.rb'
28
+
29
+ unless File.exists?(options[:file])
30
+ warn("[fail] can't find file: #{options[:file]}")
31
+ exit(1)
32
+ end
33
+
34
+ if options[:write]
35
+ cron_output = Whenever.cron(:file => options[:file])
36
+ file_path = File.expand_path(options[:file])
37
+
38
+ #Get existing crontab
39
+ command = ['crontab']
40
+ command << "-u #{options[:user]}" if options[:user]
41
+ command << "-l"
42
+
43
+ existing_crontab = `#{command.join(' ')}`
44
+ if $?.exitstatus == 0
45
+ lines = existing_crontab.split(/\n/)
46
+ kept_lines = []
47
+ keep = true
48
+ lines.each do |line|
49
+ keep = false if line =~ /#BEGIN.*#{Regexp.escape(file_path)}/
50
+
51
+ kept_lines << line if keep
52
+
53
+ keep = true if line =~ /#END.*#{Regexp.escape(file_path)}/
54
+ end
55
+ else
56
+ warn "[fail] couldn't read existing crontab"
57
+ exit(1)
58
+ end
59
+
60
+ tmp_cron_file = Tempfile.new('whenever_tmp_cron').path
61
+ File.open(tmp_cron_file, File::WRONLY | File::APPEND) do |file|
62
+ kept_lines.each do |line|
63
+ file.puts line
64
+ end
65
+ file << "#BEGIN: Crontab entry generated from: #{file_path}\n"
66
+ file << cron_output
67
+ file << "#END: Crontab entry generated from: #{file_path}\n"
68
+ end
69
+
70
+ command = ['crontab']
71
+ command << "-u #{options[:user]}" if options[:user]
72
+ command << tmp_cron_file
73
+
74
+ if system(command.join(' '))
75
+ puts "[write] crontab file updated"
76
+ exit
77
+ else
78
+ warn "[fail] couldn't write crontab"
79
+ exit(1)
80
+ end
81
+
82
+ else
83
+ puts Whenever.cron(:file => options[:file])
84
+ exit
85
+ end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This file is based heavily on Capistrano's `capify` command
4
+
5
+ require 'optparse'
6
+ require 'fileutils'
7
+
8
+ OptionParser.new do |opts|
9
+ opts.banner = "Usage: #{File.basename($0)} [path]"
10
+
11
+ begin
12
+ opts.parse!(ARGV)
13
+ rescue OptionParser::ParseError => e
14
+ warn e.message
15
+ puts opts
16
+ exit 1
17
+ end
18
+ end
19
+
20
+ if ARGV.empty?
21
+ abort "Please specify the directory to wheneverize, e.g. `#{File.basename($0)} .'"
22
+ elsif !File.exists?(ARGV.first)
23
+ abort "`#{ARGV.first}' does not exist."
24
+ elsif !File.directory?(ARGV.first)
25
+ abort "`#{ARGV.first}' is not a directory."
26
+ elsif ARGV.length > 1
27
+ abort "Too many arguments; please specify only the directory to wheneverize."
28
+ end
29
+
30
+
31
+ content = <<-FILE
32
+ # Use this file to easily define all of your cron jobs.
33
+ #
34
+ # It's helpful, but not entirely necessary to understand cron before proceeding.
35
+ # http://en.wikipedia.org/wiki/Cron
36
+
37
+ # Example:
38
+ #
39
+ # set :cron_log, "/path/to/my/cron_log.log"
40
+ #
41
+ # every 2.hours do
42
+ # command "/usr/bin/some_great_command"
43
+ # runner "MyModel.some_method"
44
+ # rake "some:great:rake:task"
45
+ # end
46
+ #
47
+ # every 4.days do
48
+ # runner "AnotherModel.prune_old_records"
49
+ # end
50
+
51
+ # Learn more: http://github.com/javan/whenever
52
+ FILE
53
+
54
+ file = 'config/schedule.rb'
55
+ base = ARGV.shift
56
+
57
+ file = File.join(base, file)
58
+ if File.exists?(file)
59
+ warn "[skip] `#{file}' already exists"
60
+ elsif File.exists?(file.downcase)
61
+ warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'"
62
+ elsif !File.exists?(File.dirname(file))
63
+ warn "[skip] directory `#{File.dirname(file)}' does not exist"
64
+ else
65
+ puts "[add] writing `#{file}'"
66
+ File.open(file, "w") { |f| f.write(content) }
67
+ end
68
+
69
+ puts "[done] wheneverized!"
@@ -0,0 +1,15 @@
1
+ module Whenever
2
+
3
+ def self.cron(options)
4
+ Whenever::JobList.new(options).generate_cron_output
5
+ end
6
+
7
+ def self.path
8
+ if defined?(RAILS_ROOT)
9
+ RAILS_ROOT
10
+ elsif defined?(::RAILS_ROOT)
11
+ ::RAILS_ROOT
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,90 @@
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 => @environment, :path => @path)
45
+ options[:class] = Whenever::Job::Runner
46
+ command(task, options)
47
+ end
48
+
49
+ def rake(task, options = {})
50
+ options.reverse_merge!(:environment => @environment, :path => @path)
51
+ options[:class] = Whenever::Job::RakeTask
52
+ command(task, options)
53
+ end
54
+
55
+ def generate_cron_output
56
+ [environment_variables, cron_jobs].compact.join
57
+ end
58
+
59
+ private
60
+
61
+ def environment_variables
62
+ return if @env.empty?
63
+
64
+ output = []
65
+ @env.each do |key, val|
66
+ output << "#{key}=#{val}\n"
67
+ end
68
+ output << "\n"
69
+
70
+ output.join
71
+ end
72
+
73
+ def cron_jobs
74
+ return if @jobs.empty?
75
+
76
+ output = []
77
+ @jobs.each do |time, jobs|
78
+ jobs.each do |job|
79
+ cron = Whenever::Output::Cron.output(time, job)
80
+ cron << " >> #{job.cron_log} 2>&1" if job.cron_log
81
+ cron << "\n\n"
82
+ output << cron
83
+ end
84
+ end
85
+
86
+ output.join
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,27 @@
1
+ module Whenever
2
+ module Job
3
+ class Default
4
+
5
+ attr_accessor :task, :at, :cron_log
6
+
7
+ def initialize(options = {})
8
+ @task = options[:task]
9
+ @at = options[:at]
10
+ @cron_log = options[:cron_log]
11
+ @environment = options[:environment] || :production
12
+ @path = options[:path] || Whenever.path
13
+ end
14
+
15
+ def output
16
+ task
17
+ end
18
+
19
+ protected
20
+
21
+ def path_required
22
+ raise ArgumentError, "No path available; set :path, '/your/path' in your schedule file" if @path.blank?
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module Whenever
2
+ module Job
3
+ class RakeTask < Whenever::Job::Default
4
+
5
+ def output
6
+ path_required
7
+ "cd #{@path} && RAILS_ENV=#{@environment} /usr/bin/env rake #{task}"
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Whenever
2
+ module Job
3
+ class Runner < Whenever::Job::Default
4
+
5
+ def output
6
+ path_required
7
+ %Q(#{File.join(@path, 'script', 'runner')} -e #{@environment} "#{task}")
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -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,9 @@
1
+ module Whenever
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 5
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end unless defined?(Whenever::VERSION)
@@ -0,0 +1,19 @@
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
+ end
11
+
12
+ # Dependencies
13
+ require 'activesupport'
14
+ require 'chronic'
15
+
16
+ # Whenever files
17
+ %w{ base version job_list job_types/default job_types/rake_task job_types/runner outputs/cron }.each do |file|
18
+ require File.expand_path(File.dirname(__FILE__) + "/#{file}")
19
+ end
@@ -0,0 +1,187 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/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 File.expand_path(File.dirname(__FILE__) + "/test_helper")
2
+
3
+ class OutputCommandTest < Test::Unit::TestCase
4
+
5
+ context "A plain command" do
6
+ setup do
7
+ @output = Whenever.cron \
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 = Whenever.cron \
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 = Whenever.cron \
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 = Whenever.cron \
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 File.expand_path(File.dirname(__FILE__) + "/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 = Whenever.cron \
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,74 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/test_helper")
2
+
3
+ class OutputRakeTest < Test::Unit::TestCase
4
+
5
+ # Rake are generated in an almost identical way to runners so we
6
+ # only need some basic tests to ensure they are output correctly
7
+
8
+ context "A rake command with path set" do
9
+ setup do
10
+ @output = Whenever.cron \
11
+ <<-file
12
+ set :path, '/my/path'
13
+ every 2.hours do
14
+ rake "blahblah"
15
+ end
16
+ file
17
+ end
18
+
19
+ should "output the rake command using that path" do
20
+ assert_match two_hours + ' cd /my/path && RAILS_ENV=production /usr/bin/env rake blahblah', @output
21
+ end
22
+ end
23
+
24
+ context "A rake command that overrides the path set" do
25
+ setup do
26
+ @output = Whenever.cron \
27
+ <<-file
28
+ set :path, '/my/path'
29
+ every 2.hours do
30
+ rake "blahblah", :path => '/some/other/path'
31
+ end
32
+ file
33
+ end
34
+
35
+ should "output the rake command using that path" do
36
+ assert_match two_hours + ' cd /some/other/path && RAILS_ENV=production /usr/bin/env rake blahblah', @output
37
+ end
38
+ end
39
+
40
+ context "A rake command with environment set" do
41
+ setup do
42
+ @output = Whenever.cron \
43
+ <<-file
44
+ set :environment, :silly
45
+ set :path, '/my/path'
46
+ every 2.hours do
47
+ rake "blahblah"
48
+ end
49
+ file
50
+ end
51
+
52
+ should "output the rake command using that environment" do
53
+ assert_match two_hours + ' cd /my/path && RAILS_ENV=silly /usr/bin/env rake blahblah', @output
54
+ end
55
+ end
56
+
57
+ context "A rake command that overrides the environment set" do
58
+ setup do
59
+ @output = Whenever.cron \
60
+ <<-file
61
+ set :environment, :silly
62
+ set :path, '/my/path'
63
+ every 2.hours do
64
+ rake "blahblah", :environment => :serious
65
+ end
66
+ file
67
+ end
68
+
69
+ should "output the rake command using that environment" do
70
+ assert_match two_hours + ' cd /my/path && RAILS_ENV=serious /usr/bin/env rake blahblah', @output
71
+ end
72
+ end
73
+
74
+ end
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/test_helper")
2
+
3
+ class OutputRunnerTest < Test::Unit::TestCase
4
+
5
+ context "A runner with path set" do
6
+ setup do
7
+ @output = Whenever.cron \
8
+ <<-file
9
+ set :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 that overrides the path set" do
22
+ setup do
23
+ @output = Whenever.cron \
24
+ <<-file
25
+ set :path, '/my/path'
26
+ every 2.hours do
27
+ runner "blahblah", :path => '/some/other/path'
28
+ end
29
+ file
30
+ end
31
+
32
+ should "output the runner using that path" do
33
+ assert_match two_hours + ' /some/other/path/script/runner -e production "blahblah"', @output
34
+ end
35
+ end
36
+
37
+ context "A runner with no path set and RAILS_ROOT defined" do
38
+ setup do
39
+ Whenever.stubs(:path).returns('/my/path')
40
+
41
+ @output = Whenever.cron \
42
+ <<-file
43
+ every 2.hours do
44
+ runner "blahblah"
45
+ end
46
+ file
47
+ end
48
+
49
+ should "output the runner using that path" do
50
+ assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output
51
+ end
52
+ end
53
+
54
+ context "A runner with path set AND RAILS_ROOT defined" do
55
+ setup do
56
+ Whenever.stubs(:path).returns('/my/rails/path')
57
+
58
+ @output = Whenever.cron \
59
+ <<-file
60
+ set :path, '/my/path'
61
+ every 2.hours do
62
+ runner "blahblah"
63
+ end
64
+ file
65
+ end
66
+
67
+ should "use the path" do
68
+ assert_match two_hours + ' /my/path/script/runner -e production "blahblah"', @output
69
+ assert_no_match /\/rails\/path/, @output
70
+ end
71
+ end
72
+
73
+ context "A runner with no path set and no RAILS_ROOT defined" do
74
+ setup do
75
+ Whenever.stubs(:path).returns(nil)
76
+
77
+ @input = <<-file
78
+ every 2.hours do
79
+ runner "blahblah"
80
+ end
81
+ file
82
+ end
83
+
84
+ should "raise an exception" do
85
+ assert_raises ArgumentError do
86
+ Whenever.cron(@input)
87
+ end
88
+ end
89
+ end
90
+
91
+ context "A runner with an environment set" do
92
+ setup do
93
+ @output = Whenever.cron \
94
+ <<-file
95
+ set :environment, :silly
96
+ set :path, '/my/path'
97
+ every 2.hours do
98
+ runner "blahblah"
99
+ end
100
+ file
101
+ end
102
+
103
+ should "output the runner using that environment" do
104
+ assert_match two_hours + ' /my/path/script/runner -e silly "blahblah"', @output
105
+ end
106
+ end
107
+
108
+ context "A runner that overrides the environment set" do
109
+ setup do
110
+ @output = Whenever.cron \
111
+ <<-file
112
+ set :environment, :silly
113
+ set :path, '/my/path'
114
+ every 2.hours do
115
+ runner "blahblah", :environment => :serious
116
+ end
117
+ file
118
+ end
119
+
120
+ should "output the runner using that environment" do
121
+ assert_match two_hours + ' /my/path/script/runner -e serious "blahblah"', @output
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/whenever")
5
+
6
+ begin
7
+ require 'shoulda'
8
+ rescue LoadError
9
+ warn 'To test Whenever you need the shoulda gem:'
10
+ warn '$ sudo gem install thoughtbot-shoulda'
11
+ exit(1)
12
+ end
13
+
14
+ begin
15
+ require 'mocha'
16
+ rescue LoadError
17
+ warn 'To test Whenever you need the mocha gem:'
18
+ warn '$ sudo gem install mocha'
19
+ exit(1)
20
+ end
21
+
22
+
23
+ module TestExtensions
24
+
25
+ def two_hours
26
+ "0 0,2,4,6,8,10,12,14,16,18,20,22 * * *"
27
+ end
28
+
29
+ end
30
+
31
+ class Test::Unit::TestCase
32
+ include TestExtensions
33
+ end
@@ -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.5.1"
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-19}
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", "CHANGELOG.rdoc", "lib/base.rb", "lib/job_list.rb", "lib/job_types/default.rb", "lib/job_types/rake_task.rb", "lib/job_types/runner.rb", "lib/outputs/cron.rb", "lib/version.rb", "lib/whenever.rb", "README.rdoc"]
14
+ s.files = ["bin/whenever", "bin/wheneverize", "CHANGELOG.rdoc", "lib/base.rb", "lib/job_list.rb", "lib/job_types/default.rb", "lib/job_types/rake_task.rb", "lib/job_types/runner.rb", "lib/outputs/cron.rb", "lib/version.rb", "lib/whenever.rb", "Manifest", "Rakefile", "README.rdoc", "test/cron_test.rb", "test/output_command_test.rb", "test/output_env_test.rb", "test/output_rake_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_rake_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.2.3"])
30
+ s.add_runtime_dependency(%q<activesupport>, [">= 1.3.0"])
31
+ else
32
+ s.add_dependency(%q<chronic>, [">= 0.2.3"])
33
+ s.add_dependency(%q<activesupport>, [">= 1.3.0"])
34
+ end
35
+ else
36
+ s.add_dependency(%q<chronic>, [">= 0.2.3"])
37
+ s.add_dependency(%q<activesupport>, [">= 1.3.0"])
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: andrewtimberlake-whenever
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5.1
5
+ platform: ruby
6
+ authors:
7
+ - Javan Makhmali
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-19 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.2.3
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: 1.3.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
+ - CHANGELOG.rdoc
46
+ - lib/base.rb
47
+ - lib/job_list.rb
48
+ - lib/job_types/default.rb
49
+ - lib/job_types/rake_task.rb
50
+ - lib/job_types/runner.rb
51
+ - lib/outputs/cron.rb
52
+ - lib/version.rb
53
+ - lib/whenever.rb
54
+ - README.rdoc
55
+ files:
56
+ - bin/whenever
57
+ - bin/wheneverize
58
+ - CHANGELOG.rdoc
59
+ - lib/base.rb
60
+ - lib/job_list.rb
61
+ - lib/job_types/default.rb
62
+ - lib/job_types/rake_task.rb
63
+ - lib/job_types/runner.rb
64
+ - lib/outputs/cron.rb
65
+ - lib/version.rb
66
+ - lib/whenever.rb
67
+ - Manifest
68
+ - Rakefile
69
+ - README.rdoc
70
+ - test/cron_test.rb
71
+ - test/output_command_test.rb
72
+ - test/output_env_test.rb
73
+ - test/output_rake_test.rb
74
+ - test/output_runner_test.rb
75
+ - test/test_helper.rb
76
+ - whenever.gemspec
77
+ has_rdoc: true
78
+ homepage: http://github.com/javan/whenever
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --line-numbers
82
+ - --inline-source
83
+ - --title
84
+ - Whenever
85
+ - --main
86
+ - README.rdoc
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "1.2"
100
+ version:
101
+ requirements: []
102
+
103
+ rubyforge_project: whenever
104
+ rubygems_version: 1.2.0
105
+ signing_key:
106
+ specification_version: 2
107
+ summary: Provides clean ruby syntax for defining messy cron jobs and running them Whenever.
108
+ test_files:
109
+ - test/cron_test.rb
110
+ - test/output_command_test.rb
111
+ - test/output_env_test.rb
112
+ - test/output_rake_test.rb
113
+ - test/output_runner_test.rb
114
+ - test/test_helper.rb