hyper-smart-pkg 0.0.1

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/hyper-smart-pkg.gemspec +11 -0
  3. data/whenever-1.1.2/Appraisals +69 -0
  4. data/whenever-1.1.2/CHANGELOG.md +434 -0
  5. data/whenever-1.1.2/CONTRIBUTING.md +38 -0
  6. data/whenever-1.1.2/Gemfile +11 -0
  7. data/whenever-1.1.2/LICENSE +22 -0
  8. data/whenever-1.1.2/Makefile +5 -0
  9. data/whenever-1.1.2/README.md +350 -0
  10. data/whenever-1.1.2/Rakefile +10 -0
  11. data/whenever-1.1.2/bin/whenever +49 -0
  12. data/whenever-1.1.2/bin/wheneverize +71 -0
  13. data/whenever-1.1.2/gemfiles/activesupport5.0.gemfile +11 -0
  14. data/whenever-1.1.2/gemfiles/activesupport5.1.gemfile +11 -0
  15. data/whenever-1.1.2/gemfiles/activesupport5.2.gemfile +11 -0
  16. data/whenever-1.1.2/gemfiles/activesupport6.0.gemfile +17 -0
  17. data/whenever-1.1.2/gemfiles/activesupport6.1.gemfile +17 -0
  18. data/whenever-1.1.2/gemfiles/activesupport7.0.gemfile +11 -0
  19. data/whenever-1.1.2/gemfiles/activesupport7.1.gemfile +11 -0
  20. data/whenever-1.1.2/gemfiles/activesupport7.2.gemfile +11 -0
  21. data/whenever-1.1.2/gemfiles/activesupport8.0.gemfile +11 -0
  22. data/whenever-1.1.2/gemfiles/activesupport8.1.gemfile +11 -0
  23. data/whenever-1.1.2/lib/whenever/capistrano/v2/hooks.rb +8 -0
  24. data/whenever-1.1.2/lib/whenever/capistrano/v2/recipes.rb +49 -0
  25. data/whenever-1.1.2/lib/whenever/capistrano/v2/support.rb +53 -0
  26. data/whenever-1.1.2/lib/whenever/capistrano/v3/tasks/whenever.rake +56 -0
  27. data/whenever-1.1.2/lib/whenever/capistrano.rb +7 -0
  28. data/whenever-1.1.2/lib/whenever/command_line.rb +171 -0
  29. data/whenever-1.1.2/lib/whenever/cron.rb +181 -0
  30. data/whenever-1.1.2/lib/whenever/job.rb +56 -0
  31. data/whenever-1.1.2/lib/whenever/job_list.rb +186 -0
  32. data/whenever-1.1.2/lib/whenever/numeric.rb +13 -0
  33. data/whenever-1.1.2/lib/whenever/numeric_seconds.rb +48 -0
  34. data/whenever-1.1.2/lib/whenever/os.rb +7 -0
  35. data/whenever-1.1.2/lib/whenever/output_redirection.rb +57 -0
  36. data/whenever-1.1.2/lib/whenever/setup.rb +31 -0
  37. data/whenever-1.1.2/lib/whenever/version.rb +3 -0
  38. data/whenever-1.1.2/lib/whenever.rb +42 -0
  39. data/whenever-1.1.2/test/functional/command_line_test.rb +458 -0
  40. data/whenever-1.1.2/test/functional/output_at_test.rb +246 -0
  41. data/whenever-1.1.2/test/functional/output_default_defined_jobs_test.rb +310 -0
  42. data/whenever-1.1.2/test/functional/output_defined_job_test.rb +113 -0
  43. data/whenever-1.1.2/test/functional/output_description_test.rb +25 -0
  44. data/whenever-1.1.2/test/functional/output_env_test.rb +29 -0
  45. data/whenever-1.1.2/test/functional/output_jobs_for_roles_test.rb +65 -0
  46. data/whenever-1.1.2/test/functional/output_jobs_with_mailto_test.rb +168 -0
  47. data/whenever-1.1.2/test/functional/output_redirection_test.rb +248 -0
  48. data/whenever-1.1.2/test/test_case.rb +32 -0
  49. data/whenever-1.1.2/test/test_helper.rb +51 -0
  50. data/whenever-1.1.2/test/unit/capistrano_support_test.rb +147 -0
  51. data/whenever-1.1.2/test/unit/cron_test.rb +418 -0
  52. data/whenever-1.1.2/test/unit/executable_test.rb +142 -0
  53. data/whenever-1.1.2/test/unit/job_test.rb +114 -0
  54. data/whenever-1.1.2/whenever.gemspec +29 -0
  55. metadata +93 -0
@@ -0,0 +1,53 @@
1
+ module Whenever
2
+ module CapistranoSupport
3
+ def self.load_into(capistrano_configuration)
4
+ capistrano_configuration.load do
5
+
6
+ def whenever_options
7
+ fetch(:whenever_options)
8
+ end
9
+
10
+ def whenever_roles
11
+ Array(whenever_options[:roles])
12
+ end
13
+
14
+ def whenever_servers
15
+ find_servers(whenever_options)
16
+ end
17
+
18
+ def whenever_server_roles
19
+ whenever_servers.inject({}) do |map, server|
20
+ map[server] = role_names_for_host(server) & whenever_roles
21
+ map
22
+ end
23
+ end
24
+
25
+ def whenever_prepare_for_rollback args
26
+ if fetch(:previous_release)
27
+ # rollback to the previous release's crontab
28
+ args[:path] = fetch(:previous_release)
29
+ else
30
+ # clear the crontab if no previous release
31
+ args[:path] = fetch(:release_path)
32
+ args[:flags] = fetch(:whenever_clear_flags)
33
+ end
34
+ args
35
+ end
36
+
37
+ def whenever_run_commands(args)
38
+ unless [:command, :path, :flags].all? { |a| args.include?(a) }
39
+ raise ArgumentError, ":command, :path, & :flags are required"
40
+ end
41
+
42
+ whenever_server_roles.each do |server, roles|
43
+ roles_arg = roles.empty? ? "" : " --roles #{roles.join(',')}"
44
+
45
+ command = "cd #{args[:path]} && #{args[:command]} #{args[:flags]}#{roles_arg}"
46
+ run command, whenever_options.merge(:hosts => server)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,56 @@
1
+ namespace :whenever do
2
+ def setup_whenever_task(*args, &block)
3
+ args = Array(fetch(:whenever_command)) + args
4
+
5
+ on roles *fetch(:whenever_roles) do |host|
6
+ args_for_host = block_given? ? args + Array(yield(host)) : args
7
+ within fetch(:whenever_path) do
8
+ with fetch(:whenever_command_environment_variables) do
9
+ execute(*args_for_host)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ def load_file
16
+ file = fetch(:whenever_load_file)
17
+ if file
18
+ "-f #{file}"
19
+ else
20
+ ''
21
+ end
22
+ end
23
+
24
+ desc "Update application's crontab entries using Whenever"
25
+ task :update_crontab do
26
+ setup_whenever_task do |host|
27
+ roles = host.roles_array.join(",")
28
+ [fetch(:whenever_update_flags), "--roles=#{roles}", load_file]
29
+ end
30
+ end
31
+
32
+ desc "Clear application's crontab entries using Whenever"
33
+ task :clear_crontab do
34
+ setup_whenever_task do |host|
35
+ [fetch(:whenever_clear_flags), load_file]
36
+ end
37
+ end
38
+
39
+ after "deploy:updated", "whenever:update_crontab"
40
+ after "deploy:reverted", "whenever:update_crontab"
41
+ end
42
+
43
+ namespace :load do
44
+ task :defaults do
45
+ set :whenever_roles, ->{ :db }
46
+ set :whenever_command, ->{ [:bundle, :exec, :whenever] }
47
+ set :whenever_command_environment_variables, ->{ fetch(:default_env).merge(rails_env: fetch(:whenever_environment)) }
48
+ set :whenever_identifier, ->{ fetch :application }
49
+ set :whenever_environment, ->{ fetch :rails_env, fetch(:stage, "production") }
50
+ set :whenever_variables, ->{ "environment=#{fetch :whenever_environment}" }
51
+ set :whenever_load_file, ->{ nil }
52
+ set :whenever_update_flags, ->{ "--update-crontab #{fetch :whenever_identifier} --set #{fetch :whenever_variables}" }
53
+ set :whenever_clear_flags, ->{ "--clear-crontab #{fetch :whenever_identifier}" }
54
+ set :whenever_path, ->{ release_path }
55
+ end
56
+ end
@@ -0,0 +1,7 @@
1
+ require 'capistrano/version'
2
+
3
+ if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION).release >= Gem::Version.new('3.0.0')
4
+ load File.expand_path("../capistrano/v3/tasks/whenever.rake", __FILE__)
5
+ else
6
+ require 'whenever/capistrano/v2/hooks'
7
+ end
@@ -0,0 +1,171 @@
1
+ require 'fileutils'
2
+
3
+ module Whenever
4
+ class CommandLine
5
+ def self.execute(options={})
6
+ new(options).run
7
+ end
8
+
9
+ def initialize(options={})
10
+ @options = options
11
+
12
+ @options[:crontab_command] ||= 'crontab'
13
+ @options[:file] ||= 'config/schedule.rb'
14
+ @options[:cut] ||= 0
15
+ @options[:identifier] ||= default_identifier
16
+ @options[:console] = true if @options[:console].nil?
17
+
18
+ if !File.exist?(@options[:file]) && @options[:clear].nil?
19
+ warn("[fail] Can't find file: #{@options[:file]}")
20
+ return_or_exit(false)
21
+ end
22
+
23
+ if [@options[:update], @options[:write], @options[:clear]].compact.length > 1
24
+ warn("[fail] Can only update, write or clear. Choose one.")
25
+ return_or_exit(false)
26
+ end
27
+
28
+ unless @options[:cut].to_s =~ /[0-9]*/
29
+ warn("[fail] Can't cut negative lines from the crontab #{options[:cut]}")
30
+ return_or_exit(false)
31
+ end
32
+ @options[:cut] = @options[:cut].to_i
33
+
34
+ @timestamp = Time.now.to_s
35
+ end
36
+
37
+ def run
38
+ if @options[:update] || @options[:clear]
39
+ write_crontab(updated_crontab)
40
+ elsif @options[:write]
41
+ write_crontab(whenever_cron)
42
+ else
43
+ puts Whenever.cron(@options)
44
+ puts "## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated."
45
+ puts "## [message] Run `whenever --help' for more options."
46
+ return_or_exit(true)
47
+ end
48
+ end
49
+
50
+ protected
51
+
52
+ def default_identifier
53
+ File.expand_path(@options[:file])
54
+ end
55
+
56
+ def whenever_cron
57
+ return '' if @options[:clear]
58
+ @whenever_cron ||= [comment_open, Whenever.cron(@options), comment_close].compact.join("\n") + "\n"
59
+ end
60
+
61
+ def read_crontab
62
+ return @current_crontab if instance_variable_defined?(:@current_crontab)
63
+
64
+ command = [@options[:crontab_command]]
65
+ command << '-l'
66
+ command << "-u #{@options[:user]}" if @options[:user]
67
+
68
+ command_results = %x[#{command.join(' ')} 2> /dev/null]
69
+ @current_crontab = $?.exitstatus.zero? ? prepare(command_results) : ''
70
+ end
71
+
72
+ def write_crontab(contents)
73
+ command = [@options[:crontab_command]]
74
+ command << "-u #{@options[:user]}" if @options[:user]
75
+ # Solaris/SmartOS cron does not support the - option to read from stdin.
76
+ command << "-" unless OS.solaris?
77
+
78
+ IO.popen(command.join(' '), 'r+') do |crontab|
79
+ crontab.write(contents)
80
+ crontab.close_write
81
+ stdout = crontab.read
82
+ puts stdout unless stdout == ''
83
+ end
84
+
85
+ success = $?.exitstatus.zero?
86
+
87
+ if success
88
+ action = 'written' if @options[:write]
89
+ action = 'updated' if @options[:update]
90
+ puts "[write] crontab file #{action}"
91
+ return_or_exit(true)
92
+ else
93
+ warn "[fail] Couldn't write crontab; try running `whenever' with no options to ensure your schedule file is valid."
94
+ return_or_exit(false)
95
+ end
96
+ end
97
+
98
+ def updated_crontab
99
+ # Check for unopened or unclosed identifier blocks
100
+ if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && (read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")).nil?
101
+ warn "[fail] Unclosed indentifier; Your crontab file contains '#{comment_open(false)}', but no '#{comment_close(false)}'"
102
+ return_or_exit(false)
103
+ elsif (read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$")).nil? && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
104
+ warn "[fail] Unopened indentifier; Your crontab file contains '#{comment_close(false)}', but no '#{comment_open(false)}'"
105
+ return_or_exit(false)
106
+ end
107
+
108
+ # If an existing identifier block is found, replace it with the new cron entries
109
+ if read_crontab =~ Regexp.new("^#{comment_open_regex}\s*$") && read_crontab =~ Regexp.new("^#{comment_close_regex}\s*$")
110
+ # If the existing crontab file contains backslashes they get lost going through gsub.
111
+ # .gsub('\\', '\\\\\\') preserves them. Go figure.
112
+ read_crontab.gsub(Regexp.new("^#{comment_open_regex}\s*$.+^#{comment_close_regex}\s*$", Regexp::MULTILINE), whenever_cron.chomp.gsub('\\', '\\\\\\'))
113
+ else # Otherwise, append the new cron entries after any existing ones
114
+ [read_crontab, whenever_cron].join("\n\n")
115
+ end.gsub(/\n{3,}/, "\n\n") # More than two newlines becomes just two.
116
+ end
117
+
118
+ def prepare(contents)
119
+ # Strip n lines from the top of the file as specified by the :cut option.
120
+ # Use split with a -1 limit option to ensure the join is able to rebuild
121
+ # the file with all of the original seperators in-tact.
122
+ stripped_contents = contents.split($/,-1)[@options[:cut]..-1].join($/)
123
+
124
+ # Some cron implementations require all non-comment lines to be newline-
125
+ # terminated. (issue #95) Strip all newlines and replace with the default
126
+ # platform record seperator ($/)
127
+ stripped_contents.gsub!(/\s+$/, $/)
128
+ end
129
+
130
+ def comment_base(include_timestamp = true)
131
+ if include_timestamp
132
+ "Whenever generated tasks for: #{@options[:identifier]} at: #{@timestamp}"
133
+ else
134
+ "Whenever generated tasks for: #{@options[:identifier]}"
135
+ end
136
+ end
137
+
138
+ def comment_open(include_timestamp = true)
139
+ "# Begin #{comment_base(include_timestamp)}"
140
+ end
141
+
142
+ def comment_close(include_timestamp = true)
143
+ "# End #{comment_base(include_timestamp)}"
144
+ end
145
+
146
+ def comment_open_regex
147
+ "#{comment_open(false)}(#{timestamp_regex}|)"
148
+ end
149
+
150
+ def comment_close_regex
151
+ "#{comment_close(false)}(#{timestamp_regex}|)"
152
+ end
153
+
154
+ def timestamp_regex
155
+ " at: \\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} ([+-]\\d{4}|UTC)"
156
+ end
157
+
158
+ private
159
+
160
+ def return_or_exit success
161
+ result = 1
162
+ result = 0 if success
163
+ if @options[:console]
164
+ exit(result)
165
+ else
166
+ result
167
+ end
168
+ end
169
+
170
+ end
171
+ end
@@ -0,0 +1,181 @@
1
+ require 'chronic'
2
+
3
+ module Whenever
4
+ module Output
5
+ class Cron
6
+ DAYS = %w(sun mon tue wed thu fri sat)
7
+ MONTHS = %w(jan feb mar apr may jun jul aug sep oct nov dec)
8
+ KEYWORDS = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
9
+ REGEX = /^(@(#{KEYWORDS.join '|'})|((\*?[\d\/,\-]*)\s){3}(\*?([\d\/,\-]|(#{MONTHS.join '|'}))*\s)(\*?([\d\/,\-]|(#{DAYS.join '|'}))*))$/i
10
+
11
+ attr_accessor :time, :task
12
+
13
+ def initialize(time = nil, task = nil, at = nil, options = {})
14
+ chronic_options = options[:chronic_options] || {}
15
+
16
+ @at_given = at
17
+ @time = time
18
+ @task = task
19
+ @at = at.is_a?(String) ? (Chronic.parse(at, chronic_options) || 0) : (at || 0)
20
+ end
21
+
22
+ def self.enumerate(item, detect_cron = true)
23
+ if item and item.is_a?(String)
24
+ items =
25
+ if detect_cron && item =~ REGEX
26
+ [item]
27
+ else
28
+ item.split(',')
29
+ end
30
+ else
31
+ items = item
32
+ items = [items] unless items and items.respond_to?(:each)
33
+ end
34
+ items
35
+ end
36
+
37
+ def self.output(times, job, options = {})
38
+ enumerate(times).each do |time|
39
+ enumerate(job.at, false).each do |at|
40
+ yield new(time, job.output, at, options).output
41
+ end
42
+ end
43
+ end
44
+
45
+ def output
46
+ [time_in_cron_syntax, task].compact.join(' ').strip
47
+ end
48
+
49
+ def time_in_cron_syntax
50
+ @time = @time.to_i if @time.is_a?(Numeric) # Compatibility with `1.day` format using ruby 2.3 and activesupport
51
+ case @time
52
+ when REGEX then @time # raw cron syntax given
53
+ when Symbol then parse_symbol
54
+ when String then parse_as_string
55
+ else parse_time
56
+ end
57
+ end
58
+
59
+ protected
60
+ def day_given?
61
+ @at_given.is_a?(String) && (MONTHS.any? { |m| @at_given.downcase.index(m) } || @at_given[/\d\/\d/])
62
+ end
63
+
64
+ def parse_symbol
65
+ shortcut = case @time
66
+ when *KEYWORDS then "@#{@time}" # :reboot => '@reboot'
67
+ when :year then Whenever.seconds(1, :year)
68
+ when :day then Whenever.seconds(1, :day)
69
+ when :month then Whenever.seconds(1, :month)
70
+ when :week then Whenever.seconds(1, :week)
71
+ when :hour then Whenever.seconds(1, :hour)
72
+ when :minute then Whenever.seconds(1, :minute)
73
+ end
74
+
75
+ if shortcut.is_a?(Numeric)
76
+ @time = shortcut
77
+ parse_time
78
+ elsif shortcut
79
+ if @at.is_a?(Time) || (@at.is_a?(Numeric) && @at > 0)
80
+ raise ArgumentError, "You cannot specify an ':at' when using the shortcuts for times."
81
+ else
82
+ return shortcut
83
+ end
84
+ else
85
+ parse_as_string
86
+ end
87
+ end
88
+
89
+ def parse_time
90
+ timing = Array.new(5, '*')
91
+ case @time
92
+ when Whenever.seconds(0, :seconds)...Whenever.seconds(1, :minute)
93
+ raise ArgumentError, "Time must be in minutes or higher"
94
+ when Whenever.seconds(1, :minute)...Whenever.seconds(1, :hour)
95
+ minute_frequency = @time / 60
96
+ timing[0] = comma_separated_timing(minute_frequency, 59, @at || 0)
97
+ when Whenever.seconds(1, :hour)...Whenever.seconds(1, :day)
98
+ hour_frequency = (@time / 60 / 60).round
99
+ timing[0] = @at.is_a?(Time) ? @at.min : range_or_integer(@at, 0..59, 'Minute')
100
+ timing[1] = comma_separated_timing(hour_frequency, 23)
101
+ when Whenever.seconds(1, :day)...Whenever.seconds(1, :month)
102
+ day_frequency = (@time / 24 / 60 / 60).round
103
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
104
+ timing[1] = @at.is_a?(Time) ? @at.hour : range_or_integer(@at, 0..23, 'Hour')
105
+ timing[2] = comma_separated_timing(day_frequency, 31, 1)
106
+ when Whenever.seconds(1, :month)...Whenever.seconds(1, :year)
107
+ month_frequency = (@time / 30 / 24 / 60 / 60).round
108
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
109
+ timing[1] = @at.is_a?(Time) ? @at.hour : 0
110
+ timing[2] = if @at.is_a?(Time)
111
+ day_given? ? @at.day : 1
112
+ else
113
+ @at == 0 ? 1 : range_or_integer(@at, 1..31, 'Day')
114
+ end
115
+ timing[3] = comma_separated_timing(month_frequency, 12, 1)
116
+ when Whenever.seconds(1, :year)
117
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
118
+ timing[1] = @at.is_a?(Time) ? @at.hour : 0
119
+ timing[2] = if @at.is_a?(Time)
120
+ day_given? ? @at.day : 1
121
+ else
122
+ 1
123
+ end
124
+ timing[3] = if @at.is_a?(Time)
125
+ day_given? ? @at.month : 1
126
+ else
127
+ @at == 0 ? 1 : range_or_integer(@at, 1..12, 'Month')
128
+ end
129
+ else
130
+ return parse_as_string
131
+ end
132
+ timing.join(' ')
133
+ end
134
+
135
+ def parse_as_string
136
+ return unless @time
137
+ string = @time.to_s
138
+
139
+ timing = Array.new(4, '*')
140
+ timing[0] = @at.is_a?(Time) ? @at.min : 0
141
+ timing[1] = @at.is_a?(Time) ? @at.hour : 0
142
+
143
+ return (timing << '1-5') * " " if string.downcase.index('weekday')
144
+ return (timing << '6,0') * " " if string.downcase.index('weekend')
145
+
146
+ DAYS.each_with_index do |day, i|
147
+ return (timing << i) * " " if string.downcase.index(day)
148
+ end
149
+
150
+ raise ArgumentError, "Couldn't parse: #{@time.inspect}"
151
+ end
152
+
153
+ def range_or_integer(at, valid_range, name)
154
+ must_be_between = "#{name} must be between #{valid_range.min}-#{valid_range.max}"
155
+ if at.is_a?(Range)
156
+ raise ArgumentError, "#{must_be_between}, #{at.min} given" unless valid_range.include?(at.min)
157
+ raise ArgumentError, "#{must_be_between}, #{at.max} given" unless valid_range.include?(at.max)
158
+ return "#{at.min}-#{at.max}"
159
+ end
160
+ raise ArgumentError, "#{must_be_between}, #{at} given" unless valid_range.include?(at)
161
+ at
162
+ end
163
+
164
+ def comma_separated_timing(frequency, max, start = 0)
165
+ return start if frequency.nil? || frequency == "" || frequency.zero?
166
+ return '*' if frequency == 1
167
+ return frequency if frequency > (max * 0.5).ceil
168
+
169
+ original_start = start
170
+
171
+ start += frequency unless (max + 1).modulo(frequency).zero? || start > 0
172
+ output = (start..max).step(frequency).to_a
173
+
174
+ max_occurances = (max.to_f / (frequency.to_f)).round
175
+ max_occurances += 1 if original_start.zero?
176
+
177
+ output[0, max_occurances].join(',')
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,56 @@
1
+ require 'shellwords'
2
+
3
+ module Whenever
4
+ class Job
5
+ attr_reader :at, :roles, :mailto, :description
6
+
7
+ def initialize(options = {})
8
+ @options = options
9
+ @at = options.delete(:at)
10
+ @template = options.delete(:template)
11
+ @mailto = options.fetch(:mailto, :default_mailto)
12
+ @job_template = options.delete(:job_template) || ":job"
13
+ @roles = Array(options.delete(:roles))
14
+ @description = options.delete(:description)
15
+ @options[:output] = options.has_key?(:output) ? Whenever::Output::Redirection.new(options[:output]).to_s : ''
16
+ @options[:environment_variable] ||= "RAILS_ENV"
17
+ @options[:environment] ||= :production
18
+ @options[:path] = Shellwords.shellescape(@options[:path] || Whenever.path)
19
+ end
20
+
21
+ def output
22
+ job = process_template(@template, @options)
23
+ out = process_template(@job_template, @options.merge(:job => job))
24
+ out.gsub(/%/, '\%')
25
+ end
26
+
27
+ def has_role?(role)
28
+ roles.empty? || roles.include?(role)
29
+ end
30
+
31
+ protected
32
+
33
+ def process_template(template, options)
34
+ template.gsub(/:\w+/) do |key|
35
+ before_and_after = [$`[-1..-1], $'[0..0]]
36
+ option = options[key.sub(':', '').to_sym] || key
37
+
38
+ if before_and_after.all? { |c| c == "'" }
39
+ escape_single_quotes(option)
40
+ elsif before_and_after.all? { |c| c == '"' }
41
+ escape_double_quotes(option)
42
+ else
43
+ option
44
+ end
45
+ end.gsub(/\s+/m, " ").strip
46
+ end
47
+
48
+ def escape_single_quotes(str)
49
+ str.gsub(/'/) { "'\\''" }
50
+ end
51
+
52
+ def escape_double_quotes(str)
53
+ str.gsub(/"/) { '\"' }
54
+ end
55
+ end
56
+ end