aughr-whenever 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ == 0.2.2 / April 30th, 2009
2
+
3
+ * Days of week jobs can now accept an :at directive (ex: every :monday, :at => '5pm'). [David Eisinger]
4
+
5
+ * Fixed command line test so it runs without a config/schedule.rb present. [Javan Makhmali]
6
+
7
+ * Raising an exception if someone tries to specify an :at with a cron shortcut (:day, :reboot, etc) so there are no false hopes. [Javan Makhmali]
8
+
9
+
10
+ == 0.1.7 / March 5th, 2009
11
+
12
+ * Added ability to update the crontab file non-destuctively instead of only overwriting it. [Javan Makhmali -- Inspired by code submitted individually from: Tien Dung (tiendung), Tom Lea (cwninja), Kyle Maxwell (fizx), and Andrew Timberlake (andrewtimberlake) on github]
13
+
14
+
15
+ == 0.1.5 / February 19th, 2009
16
+
17
+ * Fixed load path so Whenever's files don't conflict with anything in Rails. Thanks Ryan Koopmans. [Javan Makhmali]
18
+
19
+
20
+ == 0.1.4 / February 17th, 2009
21
+
22
+ * Added --load-file and --user opts to whenever binary. [Javan Makhmali]
23
+
24
+
25
+ == 0.1.3 / February 16th, 2009
26
+
27
+ * Added 'rake' helper for defining scheduled rake tasks. [Javan Makhmali]
28
+
29
+ * Renamed :cron_environment and :cron_path to :enviroment and :path for better (word) compatibility with rake tasks. [Javan Makhmali]
30
+
31
+ * Improved test load paths so tests can be run individually. [Javan Makhmali]
32
+
33
+ * Got rid of already initialized constant warning. [Javan Makhmali]
34
+
35
+ * Requiring specific gem versions: Chronic >=0.2.3 and activesupport >= 1.3.0 [Javan Makhmali]
36
+
37
+
38
+ == 0.1.0 / February 15th, 2009
39
+
40
+ * Initial release [Javan Makhmali]
@@ -0,0 +1,23 @@
1
+ bin/whenever
2
+ bin/wheneverize
3
+ CHANGELOG.rdoc
4
+ lib/base.rb
5
+ lib/command_line.rb
6
+ lib/job_list.rb
7
+ lib/job_types/default.rb
8
+ lib/job_types/rake_task.rb
9
+ lib/job_types/runner.rb
10
+ lib/outputs/cron.rb
11
+ lib/version.rb
12
+ lib/whenever.rb
13
+ Rakefile
14
+ README.rdoc
15
+ test/command_line_test.rb
16
+ test/cron_test.rb
17
+ test/output_command_test.rb
18
+ test/output_env_test.rb
19
+ test/output_rake_test.rb
20
+ test/output_runner_test.rb
21
+ test/test_helper.rb
22
+ whenever.gemspec
23
+ Manifest
@@ -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, :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, :at => '12pm' 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:update_crontab"
78
+
79
+ namespace :deploy do
80
+ desc "Update the crontab file"
81
+ task :update_crontab, :roles => :db do
82
+ run "cd #{release_path} && whenever --update-crontab #{application}"
83
+ end
84
+ end
85
+
86
+ This will update your crontab file, leaving any existing entries unharmed. When using the <code>--update-crontab</code> option, Whenever will only update the entries in your crontab file related to the current schedule.rb file. You can replace the <code>#{application}</code> with any identifying string you'd like. You can have any number of apps deploy to the same crontab file peacefully given they each use a different identifier.
87
+
88
+ If you wish to simply overwrite your crontab file each time you deploy, use the <code>--write-crontab</code> option. This is ideal if you are only working with one app and every crontab entry is contained in a single schedule.rb file.
89
+
90
+ By mixing and matching the <code>--load-file</code> and <code>--user</code> 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!
91
+
92
+ == Credit
93
+
94
+ 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
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
+ 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,32 @@
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('-i', '--update-crontab [identifier]', 'Default: full path to schedule.rb file') do |identifier|
18
+ options[:update] = true
19
+ options[:identifier] = identifier if identifier
20
+ end
21
+ opts.on('-f', '--load-file [schedule file]', 'Default: config/schedule.rb') do |file|
22
+ options[:file] = file if file
23
+ end
24
+ opts.on('-u', '--user [user]', 'Default: current user') do |user|
25
+ options[:user] = user if user
26
+ end
27
+ opts.on('-e', '--env [environment]', 'Default: production') do |env|
28
+ options[:environment] = env if env
29
+ end
30
+ end.parse!
31
+
32
+ Whenever::CommandLine.execute(options)
@@ -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,109 @@
1
+ module Whenever
2
+ class CommandLine
3
+
4
+ def self.execute(options={})
5
+ new(options).run
6
+ end
7
+
8
+ def initialize(options={})
9
+ @options = options
10
+
11
+ @options[:file] ||= 'config/schedule.rb'
12
+ @options[:identifier] ||= default_identifier
13
+ @options[:environment] ||= :production
14
+
15
+ unless File.exists?(@options[:file])
16
+ warn("[fail] Can't find file: #{@options[:file]}")
17
+ exit(1)
18
+ end
19
+
20
+ if @options[:update] && @options[:write]
21
+ warn("[fail] Can't update AND write. choose one.")
22
+ exit(1)
23
+ end
24
+ end
25
+
26
+ def run
27
+ if @options[:update]
28
+ write_crontab(updated_crontab)
29
+ elsif @options[:write]
30
+ write_crontab(whenever_cron)
31
+ else
32
+ puts Whenever.cron(:file => @options[:file], :environment => @options[:environment])
33
+ exit
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def default_identifier
40
+ File.expand_path(@options[:file])
41
+ end
42
+
43
+ def whenever_cron
44
+ @whenever_cron ||= [comment_open, Whenever.cron(:file => @options[:file], :environment => @options[:environment]), comment_close].join("\n")
45
+ end
46
+
47
+ def read_crontab
48
+ return @current_crontab if @current_crontab
49
+
50
+ command = ['crontab -l']
51
+ command << "-u #{@options[:user]}" if @options[:user]
52
+
53
+ command_results = %x[#{command.join(' ')} 2> /dev/null]
54
+ @current_crontab = $?.exitstatus.zero? ? command_results : ''
55
+ end
56
+
57
+ def write_crontab(contents)
58
+ tmp_cron_file = Tempfile.new('whenever_tmp_cron').path
59
+ File.open(tmp_cron_file, File::WRONLY | File::APPEND) do |file|
60
+ file << contents
61
+ end
62
+
63
+ command = ['crontab']
64
+ command << "-u #{@options[:user]}" if @options[:user]
65
+ command << tmp_cron_file
66
+
67
+ if system(command.join(' '))
68
+ action = 'written' if @options[:write]
69
+ action = 'updated' if @options[:update]
70
+ puts "[write] crontab file #{action}"
71
+ exit
72
+ else
73
+ warn "[fail] Couldn't write crontab; try running `whenever' with no options to ensure your schedule file is valid."
74
+ exit(1)
75
+ end
76
+ end
77
+
78
+ def updated_crontab
79
+ # Check for unopened or unclosed identifier blocks
80
+ if read_crontab.index(comment_open) && !read_crontab.index(comment_close)
81
+ warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open}', but no '#{comment_close}'"
82
+ exit(1)
83
+ elsif !read_crontab.index(comment_open) && read_crontab.index(comment_close)
84
+ warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close}', but no '#{comment_open}'"
85
+ exit(1)
86
+ end
87
+
88
+ # If an existing identier block is found, replace it with the new cron entries
89
+ if read_crontab.index(comment_open) && read_crontab.index(comment_close)
90
+ read_crontab.gsub(Regexp.new("#{comment_open}.+#{comment_close}", Regexp::MULTILINE), whenever_cron)
91
+ else # Otherwise, append the new cron entries after any existing ones
92
+ [read_crontab, whenever_cron].join("\n\n")
93
+ end
94
+ end
95
+
96
+ def comment_base
97
+ "Whenever generated tasks for: #{@options[:identifier]}"
98
+ end
99
+
100
+ def comment_open
101
+ "# Begin #{comment_base}"
102
+ end
103
+
104
+ def comment_close
105
+ "# End #{comment_base}"
106
+ end
107
+
108
+ end
109
+ end
@@ -0,0 +1,92 @@
1
+ module Whenever
2
+ class JobList
3
+
4
+ def initialize(options)
5
+ @jobs = Hash.new
6
+ @env = Hash.new
7
+
8
+ @environment = options[:environment]
9
+
10
+ config = case options
11
+ when String then options
12
+ when Hash
13
+ if options[:string]
14
+ options[:string]
15
+ elsif options[:file]
16
+ File.read(options[:file])
17
+ end
18
+ end
19
+
20
+ eval(config)
21
+ end
22
+
23
+ def set(variable, value)
24
+ instance_variable_set("@#{variable}".to_sym, value)
25
+ self.class.send(:attr_reader, variable.to_sym)
26
+ end
27
+
28
+ def env(variable, value)
29
+ @env[variable.to_s] = value
30
+ end
31
+
32
+ def every(frequency, options = {})
33
+ @current_time_scope = frequency
34
+ @options = options
35
+ yield
36
+ end
37
+
38
+ def command(task, options = {})
39
+ options[:cron_log] ||= @cron_log unless options[:cron_log] === false
40
+ options[:class] ||= Whenever::Job::Default
41
+ @jobs[@current_time_scope] ||= []
42
+ @jobs[@current_time_scope] << options[:class].new(@options.merge(:task => task).merge(options))
43
+ end
44
+
45
+ def runner(task, options = {})
46
+ options.reverse_merge!(:environment => @environment, :path => @path)
47
+ options[:class] = Whenever::Job::Runner
48
+ command(task, options)
49
+ end
50
+
51
+ def rake(task, options = {})
52
+ options.reverse_merge!(:environment => @environment, :path => @path)
53
+ options[:class] = Whenever::Job::RakeTask
54
+ command(task, options)
55
+ end
56
+
57
+ def generate_cron_output
58
+ [environment_variables, cron_jobs].compact.join
59
+ end
60
+
61
+ private
62
+
63
+ def environment_variables
64
+ return if @env.empty?
65
+
66
+ output = []
67
+ @env.each do |key, val|
68
+ output << "#{key}=#{val}\n"
69
+ end
70
+ output << "\n"
71
+
72
+ output.join
73
+ end
74
+
75
+ def cron_jobs
76
+ return if @jobs.empty?
77
+
78
+ output = []
79
+ @jobs.each do |time, jobs|
80
+ jobs.each do |job|
81
+ cron = Whenever::Output::Cron.output(time, job)
82
+ cron << " >> #{job.cron_log} 2>&1" if job.cron_log
83
+ cron << "\n\n"
84
+ output << cron
85
+ end
86
+ end
87
+
88
+ output.join
89
+ end
90
+
91
+ end
92
+ end