talentbox-whenever 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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