cwninja-whenever 0.1.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -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]
data/README.rdoc ADDED
@@ -0,0 +1,127 @@
1
+ == Introduction
2
+
3
+ Whenever is a ruby gem that provides a ruby 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 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 "DB.Backup"
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
+ You can use --update-crontab to simply update whenever generated tasks.
91
+
92
+ == Credit
93
+
94
+ Whenever was created for use at Inkling (http://inklingmarkets.com) where I work.
95
+
96
+ While building Whenever, I learned a lot by digging through the source code of Capistrano - http://github.com/jamis/capistrano
97
+
98
+ == Feedback
99
+
100
+ Lighthouse: http://javan.lighthouseapp.com/projects/25781-whenever/overview
101
+
102
+ Email me: javan [at] javan (dot) us
103
+
104
+ == License
105
+
106
+ Copyright (c) 2009 Javan Makhmali
107
+
108
+ Permission is hereby granted, free of charge, to any person
109
+ obtaining a copy of this software and associated documentation
110
+ files (the "Software"), to deal in the Software without
111
+ restriction, including without limitation the rights to use,
112
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
113
+ copies of the Software, and to permit persons to whom the
114
+ Software is furnished to do so, subject to the following
115
+ conditions:
116
+
117
+ The above copyright notice and this permission notice shall be
118
+ included in all copies or substantial portions of the Software.
119
+
120
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
121
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
122
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
123
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
124
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
125
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
126
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
127
+ OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ begin
6
+ require 'echoe'
7
+
8
+ require File.expand_path(File.dirname(__FILE__) + "/lib/version")
9
+
10
+ Echoe.new('whenever', Whenever::VERSION::STRING) do |p|
11
+ p.description = "Provides clean ruby syntax for defining messy cron jobs and running them Whenever."
12
+ p.url = "http://github.com/javan/whenever"
13
+ p.author = "Javan Makhmali"
14
+ p.email = "javan@javan.us"
15
+ p.dependencies = ["chronic >=0.2.3", "activesupport >=1.3.0"]
16
+ end
17
+ rescue LoadError
18
+ puts "Could not load echoe"
19
+ end
20
+
21
+ Rake::TestTask.new do |t|
22
+ t.test_files = FileList['test/*_test.rb']
23
+ t.verbose = true
24
+ end
25
+
26
+ task :default => [:test]
data/bin/whenever ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+ require 'fileutils'
6
+ require 'tempfile'
7
+ require 'whenever'
8
+
9
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/version")
10
+
11
+ options = Hash.new
12
+
13
+ OptionParser.new do |opts|
14
+ opts.banner = "Usage: whenever [options]"
15
+ opts.on('-v', '--version') { puts "Whenever v#{Whenever::VERSION::STRING}"; exit }
16
+ opts.on('-w', '--write-crontab') { options[:write] = true }
17
+ opts.on('-p', '--update-crontab') { options[:update] = true }
18
+ opts.on('-f', '--load-file [schedule file]', 'default: config/schedule.rb') do |file|
19
+ options[:file] = file if file
20
+ end
21
+ opts.on('-u', '--user [user]', 'default: current user') do |user|
22
+ options[:user] = user if user
23
+ end
24
+ end.parse!
25
+
26
+ options[:file] ||= 'config/schedule.rb'
27
+
28
+ unless File.exists?(options[:file])
29
+ warn("[fail] can't find file: #{options[:file]}")
30
+ exit(1)
31
+ end
32
+
33
+ command_line = CommandLine.new(options)
34
+
35
+ if options[:write]
36
+ command_line.write!
37
+ elsif options[:update]
38
+ command_line.update!
39
+ else
40
+ puts Whenever.cron(:file => options[:file])
41
+ exit
42
+ end
data/bin/wheneverize ADDED
@@ -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!"
data/lib/base.rb ADDED
@@ -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,68 @@
1
+
2
+ class CommandLine
3
+ START_MARKER = "### BEGIN whenever generated crontab ###"
4
+ END_MARKER = "### END whenever generated crontab ###"
5
+
6
+ attr_reader :options
7
+
8
+ def initialize(options = {})
9
+ @options = options
10
+ end
11
+
12
+ def write!
13
+ cron_output = Whenever.cron(:file => options[:file])
14
+ write_crontab(cron_output)
15
+ end
16
+
17
+ def update!
18
+ before, after = strip_whenever_crontab(read_crontab)
19
+ whenever_cron = Whenever.cron(:file => options[:file])
20
+ write_crontab((before + [START_MARKER, whenever_cron, END_MARKER] + after).compact.join("\n"))
21
+ end
22
+
23
+ private
24
+ def strip_whenever_crontab(existing_crontab)
25
+ return [], [] if existing_crontab.nil? or existing_crontab == ""
26
+ lines = existing_crontab.split("\n")
27
+ if start = lines.index(START_MARKER)
28
+ if finish = lines.index(END_MARKER)
29
+ return lines[0...start], lines[(finish + 1)..-1]
30
+ else
31
+ warn "[fail] could not find END marker in existing crontab"
32
+ exit(1)
33
+ end
34
+ else
35
+ return lines, []
36
+ end
37
+ end
38
+
39
+ def read_crontab
40
+ command = ['crontab']
41
+ command << "-u #{options[:user]}" if options[:user]
42
+ command << "-l"
43
+
44
+ IO.popen(command.join(' ')) do |io|
45
+ return io.read
46
+ end
47
+ end
48
+
49
+ def write_crontab(data)
50
+ tmp_cron_file = Tempfile.new('whenever_tmp_cron').path
51
+ File.open(tmp_cron_file, File::WRONLY | File::APPEND) do |file|
52
+ file.puts data
53
+ end
54
+
55
+ command = ['crontab']
56
+ command << "-u #{options[:user]}" if options[:user]
57
+ command << tmp_cron_file
58
+
59
+ if system(command.join(' '))
60
+ puts "[write] crontab file updated"
61
+ exit
62
+ else
63
+ warn "[fail] couldn't write crontab"
64
+ exit(1)
65
+ end
66
+ end
67
+
68
+ end
data/lib/job_list.rb ADDED
@@ -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
data/lib/version.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Whenever
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 5
6
+ FORK = 1
7
+
8
+ STRING = [MAJOR, MINOR, TINY, FORK].join('.')
9
+ end
10
+ end unless defined?(Whenever::VERSION)
data/lib/whenever.rb ADDED
@@ -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 command_line}.each do |file|
18
+ require File.expand_path(File.dirname(__FILE__) + "/#{file}")
19
+ end