1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
require 'fileutils'
module Whenever
class CommandLine
def self.execute(options={})
new(options).run
end
def initialize(options={})
@options = options
@options[:crontab_command] ||= 'crontab'
@options[:file] ||= 'config/schedule.rb'
@options[:cut] ||= 0
@options[:identifier] ||= default_identifier
if !File.exist?(@options[:file]) && @options[:clear].nil?
warn("[fail] Can't find file: #{@options[:file]}")
exit(1)
end
if [@options[:update], @options[:write], @options[:clear]].compact.length > 1
warn("[fail] Can only update, write or clear. Choose one.")
exit(1)
end
unless @options[:cut].to_s =~ /[0-9]*/
warn("[fail] Can't cut negative lines from the crontab #{options[:cut]}")
exit(1)
end
@options[:cut] = @options[:cut].to_i
@timestamp = Time.now.to_s
end
def run
if @options[:update] || @options[:clear]
write_crontab(updated_crontab)
elsif @options[:write]
write_crontab(whenever_cron)
else
puts Whenever.cron(@options)
puts "## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated."
puts "## [message] Run `whenever --help' for more options."
exit(0)
end
end
protected
def default_identifier
File.expand_path(@options[:file])
end
def whenever_cron
return '' if @options[:clear]
@whenever_cron ||= [comment_open, Whenever.cron(@options), comment_close].compact.join("\n") + "\n"
end
def read_crontab
return @current_crontab if instance_variable_defined?(:@current_crontab)
command = [@options[:crontab_command]]
command << '-l'
command << "-u #{@options[:user]}" if @options[:user]
command_results = %x[#{command.join(' ')} 2> /dev/null]
@current_crontab = $?.exitstatus.zero? ? prepare(command_results) : ''
end
def write_crontab(contents)
command = [@options[:crontab_command]]
command << "-u #{@options[:user]}" if @options[:user]
# Solaris/SmartOS cron does not support the - option to read from stdin.
command << "-" unless OS.solaris?
IO.popen(command.join(' '), 'r+') do |crontab|
crontab.write(contents)
crontab.close_write
end
success = $?.exitstatus.zero?
if success
action = 'written' if @options[:write]
action = 'updated' if @options[:update]
puts "[write] crontab file #{action}"
exit(0)
else
warn "[fail] Couldn't write crontab; try running `whenever' with no options to ensure your schedule file is valid."
exit(1)
end
end
def updated_crontab
# Check for unopened or unclosed identifier blocks
if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && (read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")).nil?
warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open(false)}', but no '#{comment_close(false)}'"
exit(1)
elsif (read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$")).nil? && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close(false)}', but no '#{comment_open(false)}'"
exit(1)
end
# If an existing identifier block is found, replace it with the new cron entries
if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
# If the existing crontab file contains backslashes they get lost going through gsub.
# .gsub('\\', '\\\\\\') preserves them. Go figure.
read_crontab.gsub(Regexp.new("^#{comment_open_regex}\s*$.+^#{comment_close_regex}\s*$", Regexp::MULTILINE), whenever_cron.chomp.gsub('\\', '\\\\\\'))
else # Otherwise, append the new cron entries after any existing ones
[read_crontab, whenever_cron].join("\n\n")
end.gsub(/\n{3,}/, "\n\n") # More than two newlines becomes just two.
end
def prepare(contents)
# Strip n lines from the top of the file as specified by the :cut option.
# Use split with a -1 limit option to ensure the join is able to rebuild
# the file with all of the original seperators in-tact.
stripped_contents = contents.split($/,-1)[@options[:cut]..-1].join($/)
# Some cron implementations require all non-comment lines to be newline-
# terminated. (issue #95) Strip all newlines and replace with the default
# platform record seperator ($/)
stripped_contents.gsub!(/\s+$/, $/)
end
def comment_base(include_timestamp = true)
if include_timestamp
"Whenever generated tasks for: #{@options[:identifier]} at: #{@timestamp}"
else
"Whenever generated tasks for: #{@options[:identifier]}"
end
end
def comment_open(include_timestamp = true)
"# Begin #{comment_base(include_timestamp)}"
end
def comment_close(include_timestamp = true)
"# End #{comment_base(include_timestamp)}"
end
def comment_open_regex
"#{comment_open(false)}(#{timestamp_regex}|)"
end
def comment_close_regex
"#{comment_close(false)}(#{timestamp_regex}|)"
end
def timestamp_regex
" at: \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} ([+-]\\d{4}|UTC)"
end
end
end
|