adg-whenever 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -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]
data/Manifest ADDED
@@ -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
data/README.rdoc ADDED
@@ -0,0 +1,135 @@
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
+ == ADG changes
93
+
94
+ Added a ScheduledJob class (the Job namespace is taken).
95
+
96
+ Added methods on JobList to: compose ScheduledJob objects, fetch the whenever syntax schedule for a given task.
97
+
98
+ ScheduledJob is similar to the @jobs hash that JobList holds, with the main difference being that it holds its own time/frequency data.
99
+
100
+ The overall approach of ADG changes, at least for now, is to add to existing behavior, not modify it.
101
+
102
+ == Credit
103
+
104
+ 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
105
+
106
+ While building Whenever, I learned a lot by digging through the source code of Capistrano - http://github.com/jamis/capistrano
107
+
108
+ == Feedback
109
+
110
+ Email me: javan [at] javan (dot) us
111
+
112
+ == License
113
+
114
+ Copyright (c) 2009 Javan Makhmali
115
+
116
+ Permission is hereby granted, free of charge, to any person
117
+ obtaining a copy of this software and associated documentation
118
+ files (the "Software"), to deal in the Software without
119
+ restriction, including without limitation the rights to use,
120
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
121
+ copies of the Software, and to permit persons to whom the
122
+ Software is furnished to do so, subject to the following
123
+ conditions:
124
+
125
+ The above copyright notice and this permission notice shall be
126
+ included in all copies or substantial portions of the Software.
127
+
128
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
129
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
130
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
131
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
132
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
133
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
134
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
135
+ OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -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
data/bin/whenever ADDED
@@ -0,0 +1,29 @@
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
+ end.parse!
28
+
29
+ Whenever::CommandLine.execute(options)
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,39 @@
1
+ module Whenever
2
+
3
+ # Add more intervals here as we decide to support them for editing
4
+ #
5
+ # Intervals supported by Whenever::Output::Cron:
6
+ # Times
7
+ # - minutes
8
+ # - hours
9
+ # - days
10
+ # - months
11
+ # Symbols
12
+ # - :reboot
13
+ # - :year, :yearly
14
+ # - :day, :daily
15
+ # - :midnight
16
+ # - :month, :monthly
17
+ # - :week, :weekly
18
+ # - :hour, :hourly
19
+ # Strings
20
+ # - sun, mon, tue, wed, thu, fri, sat
21
+ # - weekday
22
+ # - weekend
23
+ def self.intervals
24
+ %w(days months)
25
+ end
26
+
27
+ def self.cron(options)
28
+ Whenever::JobList.new(options).generate_cron_output
29
+ end
30
+
31
+ def self.path
32
+ if defined?(RAILS_ROOT)
33
+ RAILS_ROOT
34
+ elsif defined?(::RAILS_ROOT)
35
+ ::RAILS_ROOT
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,108 @@
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
+
14
+ unless File.exists?(@options[:file])
15
+ warn("[fail] Can't find file: #{@options[:file]}")
16
+ exit(1)
17
+ end
18
+
19
+ if @options[:update] && @options[:write]
20
+ warn("[fail] Can't update AND write. choose one.")
21
+ exit(1)
22
+ end
23
+ end
24
+
25
+ def run
26
+ if @options[:update]
27
+ write_crontab(updated_crontab)
28
+ elsif @options[:write]
29
+ write_crontab(whenever_cron)
30
+ else
31
+ puts Whenever.cron(:file => @options[:file])
32
+ exit
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def default_identifier
39
+ File.expand_path(@options[:file])
40
+ end
41
+
42
+ def whenever_cron
43
+ @whenever_cron ||= [comment_open, Whenever.cron(:file => @options[:file]), comment_close].join("\n")
44
+ end
45
+
46
+ def read_crontab
47
+ return @current_crontab if @current_crontab
48
+
49
+ command = ['crontab -l']
50
+ command << "-u #{@options[:user]}" if @options[:user]
51
+
52
+ command_results = %x[#{command.join(' ')} 2> /dev/null]
53
+ @current_crontab = $?.exitstatus.zero? ? command_results : ''
54
+ end
55
+
56
+ def write_crontab(contents)
57
+ tmp_cron_file = Tempfile.new('whenever_tmp_cron').path
58
+ File.open(tmp_cron_file, File::WRONLY | File::APPEND) do |file|
59
+ file << contents
60
+ end
61
+
62
+ command = ['crontab']
63
+ command << "-u #{@options[:user]}" if @options[:user]
64
+ command << tmp_cron_file
65
+
66
+ if system(command.join(' '))
67
+ action = 'written' if @options[:write]
68
+ action = 'updated' if @options[:update]
69
+ puts "[write] crontab file #{action}"
70
+ exit
71
+ else
72
+ warn "[fail] Couldn't write crontab; try running `whenever' with no options to ensure your schedule file is valid."
73
+ exit(1)
74
+ end
75
+ end
76
+
77
+ def updated_crontab
78
+ # Check for unopened or unclosed identifier blocks
79
+ if read_crontab.index(comment_open) && !read_crontab.index(comment_close)
80
+ warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open}', but no '#{comment_close}'"
81
+ exit(1)
82
+ elsif !read_crontab.index(comment_open) && read_crontab.index(comment_close)
83
+ warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close}', but no '#{comment_open}'"
84
+ exit(1)
85
+ end
86
+
87
+ # If an existing identier block is found, replace it with the new cron entries
88
+ if read_crontab.index(comment_open) && read_crontab.index(comment_close)
89
+ read_crontab.gsub(Regexp.new("#{comment_open}.+#{comment_close}", Regexp::MULTILINE), whenever_cron)
90
+ else # Otherwise, append the new cron entries after any existing ones
91
+ [read_crontab, whenever_cron].join("\n\n")
92
+ end
93
+ end
94
+
95
+ def comment_base
96
+ "Whenever generated tasks for: #{@options[:identifier]}"
97
+ end
98
+
99
+ def comment_open
100
+ "# Begin #{comment_base}"
101
+ end
102
+
103
+ def comment_close
104
+ "# End #{comment_base}"
105
+ end
106
+
107
+ end
108
+ end