talentbox-whenever 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
data/bin/whenever ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'whenever'
5
+ require 'whenever/version'
6
+
7
+ options = {}
8
+
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: whenever [options]"
11
+ opts.on('-i', '--update-crontab [identifier]', 'Default: full path to schedule.rb file') do |identifier|
12
+ options[:update] = true
13
+ options[:identifier] = identifier if identifier
14
+ end
15
+ opts.on('-w', '--write-crontab [identifier]', 'Default: full path to schedule.rb file') do |identifier|
16
+ options[:write] = true
17
+ options[:identifier] = identifier if identifier
18
+ end
19
+ opts.on('-c', '--clear-crontab [identifier]') do |identifier|
20
+ options[:clear] = true
21
+ options[:identifier] = identifier if identifier
22
+ end
23
+ opts.on('-s', '--set [variables]', 'Example: --set \'environment=staging&path=/my/sweet/path\'') do |set|
24
+ options[:set] = set if set
25
+ end
26
+ opts.on('-f', '--load-file [schedule file]', 'Default: config/schedule.rb') do |file|
27
+ options[:file] = file if file
28
+ end
29
+ opts.on('-u', '--user [user]', 'Default: current user') do |user|
30
+ options[:user] = user if user
31
+ end
32
+ opts.on('-k', '--cut [lines]', 'Cut lines from the top of the cronfile') do |lines|
33
+ options[:cut] = lines.to_i if lines
34
+ end
35
+ opts.on('-v', '--version') { puts "Whenever v#{Whenever::VERSION}"; exit(0) }
36
+ end.parse!
37
+
38
+ Whenever::CommandLine.execute(options)
data/bin/wheneverize ADDED
@@ -0,0 +1,68 @@
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
+ unless ARGV.empty?
21
+ if !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
+ end
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 :output, "/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
+ # rake "some:great:rake:task"
44
+ # end
45
+ #
46
+ # every 4.days do
47
+ # runner "AnotherModel.prune_old_records"
48
+ # end
49
+
50
+ # Learn more: http://github.com/javan/whenever
51
+ FILE
52
+
53
+ file = 'config/schedule.rb'
54
+ base = ARGV.empty? ? '.' : ARGV.shift
55
+
56
+ file = File.join(base, file)
57
+ if File.exists?(file)
58
+ warn "[skip] `#{file}' already exists"
59
+ elsif File.exists?(file.downcase)
60
+ warn "[skip] `#{file.downcase}' exists, which could conflict with `#{file}'"
61
+ elsif !File.exists?(File.dirname(file))
62
+ warn "[skip] directory `#{File.dirname(file)}' does not exist"
63
+ else
64
+ puts "[add] writing `#{file}'"
65
+ File.open(file, "w") { |f| f.write(content) }
66
+ end
67
+
68
+ puts "[done] wheneverized!"
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activesupport", "2.3.2"
4
+ gemspec :path=>"../"
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activesupport", "2.3.4"
4
+ gemspec :path=>"../"
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activesupport", "3.0.0"
4
+ gemspec :path=>"../"
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activesupport", "3.1.0"
4
+ gemspec :path=>"../"
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activesupport", "3.2.0"
4
+ gemspec :path=>"../"
data/lib/whenever.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'thread'
2
+ require 'active_support/version'
3
+
4
+ if ActiveSupport::VERSION::STRING >= "3.0.0"
5
+ require "active_support/time"
6
+ else
7
+ require "active_support"
8
+ end
9
+
10
+ module Whenever
11
+ autoload :JobList, 'whenever/job_list'
12
+ autoload :Job, 'whenever/job'
13
+ autoload :CommandLine, 'whenever/command_line'
14
+
15
+ module Output
16
+ autoload :Cron, 'whenever/cron'
17
+ autoload :Redirection, 'whenever/output_redirection'
18
+ end
19
+
20
+ def self.cron(options)
21
+ Whenever::JobList.new(options).generate_cron_output
22
+ end
23
+
24
+ def self.path
25
+ Dir.pwd
26
+ end
27
+
28
+ def self.rails3?
29
+ File.exists?(File.join(path, 'script', 'rails'))
30
+ end
31
+
32
+ def self.bundler?
33
+ File.exists?(File.join(path, 'Gemfile'))
34
+ end
35
+ end
@@ -0,0 +1,64 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ _cset(:whenever_roles) { :db }
3
+ _cset(:whenever_options) { {:roles => fetch(:whenever_roles)} }
4
+ _cset(:whenever_command) { "whenever" }
5
+ _cset(:whenever_identifier) { fetch :application }
6
+ _cset(:whenever_environment) { fetch :rails_env, "production" }
7
+ _cset(:whenever_variables) { "environment=#{fetch :whenever_environment}" }
8
+ _cset(:whenever_update_flags) { "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables}" }
9
+ _cset(:whenever_clear_flags) { "--clear-crontab #{fetch :whenever_identifier}" }
10
+
11
+ # Disable cron jobs at the begining of a deploy.
12
+ after "deploy:update_code", "whenever:clear_crontab"
13
+ # Write the new cron jobs near the end.
14
+ before "deploy:restart", "whenever:update_crontab"
15
+ # If anything goes wrong, undo.
16
+ after "deploy:rollback", "whenever:update_crontab"
17
+
18
+ namespace :whenever do
19
+ desc <<-DESC
20
+ Update application's crontab entries using Whenever. You can configure \
21
+ the command used to invoke Whenever by setting the :whenever_command \
22
+ variable, which can be used with Bundler to set the command to \
23
+ "bundle exec whenever". You can configure the identifier used by setting \
24
+ the :whenever_identifier variable, which defaults to the same value configured \
25
+ for the :application variable. You can configure the environment by setting \
26
+ the :whenever_environment variable, which defaults to the same value \
27
+ configured for the :rails_env variable which itself defaults to "production". \
28
+ Finally, you can completely override all arguments to the Whenever command \
29
+ by setting the :whenever_update_flags variable. Additionally you can configure \
30
+ which servers the crontab is updated on by setting the :whenever_roles variable.
31
+ DESC
32
+ task :update_crontab do
33
+ options = fetch(:whenever_options)
34
+
35
+ if find_servers(options).any?
36
+ on_rollback do
37
+ if fetch :previous_release
38
+ run "cd #{fetch :previous_release} && #{fetch :whenever_command} #{fetch :whenever_update_flags}", options
39
+ else
40
+ run "cd #{fetch :release_path} && #{fetch :whenever_command} #{fetch :whenever_clear_flags}", options
41
+ end
42
+ end
43
+
44
+ run "cd #{fetch :current_path} && #{fetch :whenever_command} #{fetch :whenever_update_flags}", options
45
+ end
46
+ end
47
+
48
+ desc <<-DESC
49
+ Clear application's crontab entries using Whenever. You can configure \
50
+ the command used to invoke Whenever by setting the :whenever_command \
51
+ variable, which can be used with Bundler to set the command to \
52
+ "bundle exec whenever". You can configure the identifier used by setting \
53
+ the :whenever_identifier variable, which defaults to the same value configured \
54
+ for the :application variable. Finally, you can completely override all \
55
+ arguments to the Whenever command by setting the :whenever_clear_flags variable. \
56
+ Additionally you can configure which servers the crontab is cleared on by setting \
57
+ the :whenever_roles variable.
58
+ DESC
59
+ task :clear_crontab do
60
+ options = fetch(:whenever_options)
61
+ run "cd #{fetch :latest_release} && #{fetch :whenever_command} #{fetch :whenever_clear_flags}", options if find_servers(options).any?
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,133 @@
1
+ require 'fileutils'
2
+ require 'tempfile'
3
+
4
+ module Whenever
5
+ class CommandLine
6
+ def self.execute(options={})
7
+ new(options).run
8
+ end
9
+
10
+ def initialize(options={})
11
+ @options = options
12
+
13
+ @options[:file] ||= 'config/schedule.rb'
14
+ @options[:cut] ||= 0
15
+ @options[:identifier] ||= default_identifier
16
+
17
+ unless File.exists?(@options[:file])
18
+ warn("[fail] Can't find file: #{@options[:file]}")
19
+ exit(1)
20
+ end
21
+
22
+ if [@options[:update], @options[:write], @options[:clear]].compact.length > 1
23
+ warn("[fail] Can only update, write or clear. Choose one.")
24
+ exit(1)
25
+ end
26
+
27
+ unless @options[:cut].to_s =~ /[0-9]*/
28
+ warn("[fail] Can't cut negative lines from the crontab #{options[:cut]}")
29
+ exit(1)
30
+ end
31
+ @options[:cut] = @options[:cut].to_i
32
+ end
33
+
34
+ def run
35
+ if @options[:update] || @options[:clear]
36
+ write_crontab(updated_crontab)
37
+ elsif @options[:write]
38
+ write_crontab(whenever_cron)
39
+ else
40
+ puts Whenever.cron(@options)
41
+ puts "## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated."
42
+ puts "## [message] Run `whenever --help' for more options."
43
+ exit(0)
44
+ end
45
+ end
46
+
47
+ protected
48
+
49
+ def default_identifier
50
+ File.expand_path(@options[:file])
51
+ end
52
+
53
+ def whenever_cron
54
+ return '' if @options[:clear]
55
+ @whenever_cron ||= [comment_open, Whenever.cron(@options), comment_close].compact.join("\n") + "\n"
56
+ end
57
+
58
+ def read_crontab
59
+ return @current_crontab if @current_crontab
60
+
61
+ command = ['crontab -l']
62
+ command << "-u #{@options[:user]}" if @options[:user]
63
+
64
+ command_results = %x[#{command.join(' ')} 2> /dev/null]
65
+ @current_crontab = $?.exitstatus.zero? ? prepare(command_results) : ''
66
+ end
67
+
68
+ def write_crontab(contents)
69
+ tmp_cron_file = Tempfile.new('whenever_tmp_cron').path
70
+ File.open(tmp_cron_file, File::WRONLY | File::APPEND) do |file|
71
+ file << contents
72
+ end
73
+
74
+ command = ['crontab']
75
+ command << "-u #{@options[:user]}" if @options[:user]
76
+ command << tmp_cron_file
77
+
78
+ if system(command.join(' '))
79
+ action = 'written' if @options[:write]
80
+ action = 'updated' if @options[:update]
81
+ puts "[write] crontab file #{action}"
82
+ exit(0)
83
+ else
84
+ warn "[fail] Couldn't write crontab; try running `whenever' with no options to ensure your schedule file is valid."
85
+ exit(1)
86
+ end
87
+ end
88
+
89
+ def updated_crontab
90
+ # Check for unopened or unclosed identifier blocks
91
+ if read_crontab =~ Regexp.new("^#{comment_open}\s*$") && (read_crontab =~ Regexp.new("^#{comment_close}\s*$")).nil?
92
+ warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open}', but no '#{comment_close}'"
93
+ exit(1)
94
+ elsif (read_crontab =~ Regexp.new("^#{comment_open}\s*$")).nil? && read_crontab =~ Regexp.new("^#{comment_close}\s*$")
95
+ warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close}', but no '#{comment_open}'"
96
+ exit(1)
97
+ end
98
+
99
+ # If an existing identier block is found, replace it with the new cron entries
100
+ if read_crontab =~ Regexp.new("^#{comment_open}\s*$") && read_crontab =~ Regexp.new("^#{comment_close}\s*$")
101
+ # If the existing crontab file contains backslashes they get lost going through gsub.
102
+ # .gsub('\\', '\\\\\\') preserves them. Go figure.
103
+ read_crontab.gsub(Regexp.new("^#{comment_open}\s*$.+^#{comment_close}\s*$", Regexp::MULTILINE), whenever_cron.chomp.gsub('\\', '\\\\\\'))
104
+ else # Otherwise, append the new cron entries after any existing ones
105
+ [read_crontab, whenever_cron].join("\n\n")
106
+ end.gsub(/\n{3,}/, "\n\n") # More than two newlines becomes just two.
107
+ end
108
+
109
+ def prepare(contents)
110
+ # Strip n lines from the top of the file as specified by the :cut option.
111
+ # Use split with a -1 limit option to ensure the join is able to rebuild
112
+ # the file with all of the original seperators in-tact.
113
+ stripped_contents = contents.split($/,-1)[@options[:cut]..-1].join($/)
114
+
115
+ # Some cron implementations require all non-comment lines to be newline-
116
+ # terminated. (issue #95) Strip all newlines and replace with the default
117
+ # platform record seperator ($/)
118
+ stripped_contents.gsub!(/\s+$/, $/)
119
+ end
120
+
121
+ def comment_base
122
+ "Whenever generated tasks for: #{@options[:identifier]}"
123
+ end
124
+
125
+ def comment_open
126
+ "# Begin #{comment_base}"
127
+ end
128
+
129
+ def comment_close
130
+ "# End #{comment_base}"
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,152 @@
1
+ require 'chronic'
2
+
3
+ module Whenever
4
+ module Output
5
+ class Cron
6
+ KEYWORDS = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
7
+ REGEX = /^(@(#{KEYWORDS.join '|'})|.+ .+ .+ .+ .+.?)$/
8
+
9
+ attr_accessor :time, :task
10
+
11
+ def initialize(time = nil, task = nil, at = nil)
12
+ @at_given = at
13
+ @time = time
14
+ @task = task
15
+ @at = at.is_a?(String) ? (Chronic.parse(at) || 0) : (at || 0)
16
+ end
17
+
18
+ def self.enumerate(item, detect_cron = true)
19
+ if item and item.is_a?(String)
20
+ items =
21
+ if detect_cron && item =~ REGEX
22
+ [item]
23
+ else
24
+ item.split(',')
25
+ end
26
+ else
27
+ items = item
28
+ items = [items] unless items and items.respond_to?(:each)
29
+ end
30
+ items
31
+ end
32
+
33
+ def self.output(times, job)
34
+ enumerate(times).each do |time|
35
+ enumerate(job.at, false).each do |at|
36
+ yield new(time, job.output, at).output
37
+ end
38
+ end
39
+ end
40
+
41
+ def output
42
+ [time_in_cron_syntax, task].compact.join(' ').strip
43
+ end
44
+
45
+ def time_in_cron_syntax
46
+ case @time
47
+ when REGEX then @time # raw cron sytax given
48
+ when Symbol then parse_symbol
49
+ when String then parse_as_string
50
+ else parse_time
51
+ end
52
+ end
53
+
54
+ protected
55
+ def day_given?
56
+ months = %w(jan feb mar apr may jun jul aug sep oct nov dec)
57
+ @at_given.is_a?(String) && months.any? { |m| @at_given.downcase.index(m) }
58
+ end
59
+
60
+ def parse_symbol
61
+ shortcut = case @time
62
+ when *KEYWORDS then "@#{@time}" # :reboot => '@reboot'
63
+ when :year then 12.months
64
+ when :day then 1.day
65
+ when :month then 1.month
66
+ when :week then 1.week
67
+ when :hour then 1.hour
68
+ end
69
+
70
+ if shortcut.is_a?(Numeric)
71
+ @time = shortcut
72
+ parse_time
73
+ elsif shortcut
74
+ if @at.is_a?(Time) || (@at.is_a?(Numeric) && @at > 0)
75
+ raise ArgumentError, "You cannot specify an ':at' when using the shortcuts for times."
76
+ else
77
+ return shortcut
78
+ end
79
+ else
80
+ parse_as_string
81
+ end
82
+ end
83
+
84
+ def parse_time
85
+ timing = Array.new(5, '*')
86
+ case @time
87
+ when 0.seconds...1.minute
88
+ raise ArgumentError, "Time must be in minutes or higher"
89
+ when 1.minute...1.hour
90
+ minute_frequency = @time / 60
91
+ timing[0] = comma_separated_timing(minute_frequency, 59, @at || 0)
92
+ when 1.hour...1.day
93
+ hour_frequency = (@time / 60 / 60).round
94
+ timing[0] = @at.is_a?(Time) ? @at.min : @at
95
+ timing[1] = comma_separated_timing(hour_frequency, 23)
96
+ when 1.day...1.month
97
+ day_frequency = (@time / 24 / 60 / 60).round
98
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
99
+ timing[1] = @at.is_a?(Time) ? @at.hour : @at
100
+ timing[2] = comma_separated_timing(day_frequency, 31, 1)
101
+ when 1.month..12.months
102
+ month_frequency = (@time / 30 / 24 / 60 / 60).round
103
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
104
+ timing[1] = @at.is_a?(Time) ? @at.hour : 0
105
+ timing[2] = if @at.is_a?(Time)
106
+ day_given? ? @at.day : 1
107
+ else
108
+ @at.zero? ? 1 : @at
109
+ end
110
+ timing[3] = comma_separated_timing(month_frequency, 12, 1)
111
+ else
112
+ return parse_as_string
113
+ end
114
+ timing.join(' ')
115
+ end
116
+
117
+ def parse_as_string
118
+ return unless @time
119
+ string = @time.to_s
120
+
121
+ timing = Array.new(4, '*')
122
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
123
+ timing[1] = @at.is_a?(Time) ? @at.hour : 0
124
+
125
+ return (timing << '1-5') * " " if string.downcase.index('weekday')
126
+ return (timing << '6,0') * " " if string.downcase.index('weekend')
127
+
128
+ %w(sun mon tue wed thu fri sat).each_with_index do |day, i|
129
+ return (timing << i) * " " if string.downcase.index(day)
130
+ end
131
+
132
+ raise ArgumentError, "Couldn't parse: #{@time}"
133
+ end
134
+
135
+ def comma_separated_timing(frequency, max, start = 0)
136
+ return start if frequency.blank? || frequency.zero?
137
+ return '*' if frequency == 1
138
+ return frequency if frequency > (max * 0.5).ceil
139
+
140
+ original_start = start
141
+
142
+ start += frequency unless (max + 1).modulo(frequency).zero? || start > 0
143
+ output = (start..max).step(frequency).to_a
144
+
145
+ max_occurances = (max.to_f / (frequency.to_f)).round
146
+ max_occurances += 1 if original_start.zero?
147
+
148
+ output[0, max_occurances].join(',')
149
+ end
150
+ end
151
+ end
152
+ end