rbcrontab 0.1 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = 'rbcrontab'
9
- s.version = '0.1'
9
+ s.version = '1.0'
10
10
  s.summary = 'Generate crontabs using ruby'
11
11
  s.author = 'Rob Hurring'
12
12
  s.email = 'rob@ubrio.us'
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby -rrubygems
2
+ require 'rbcrontab'
3
+
4
+ # setup your basic file paths
5
+ ROOT = File.dirname(__FILE__)
6
+
7
+ # global setup -- we want to source our profile before any cron command
8
+ source_bash = 'source $HOME/.bash_profile'
9
+
10
+ # simple rake helper method
11
+ def rake(app, command, silent = true)
12
+ "rake -f#{ROOT}/#{app}/Rakefile #{'-s' if silent} #{command}"
13
+ end
14
+
15
+ # simple script helper method
16
+ def script(name)
17
+ "#{ROOT}/scripts/name"
18
+ end
19
+
20
+ #
21
+ # Crontab Start
22
+ #
23
+
24
+ crontab = Crontab.new do |cron|
25
+
26
+ # mail it to me
27
+ cron.mail_to 'you@example.com'
28
+ # add my custom paths
29
+ cron.path %w{/usr/local/bin /usr/bin $HOME/bin}, false
30
+ # setup my home variable
31
+ cron.home ENV['HOME']
32
+ # make sure every schedule sources the bash profile and sets
33
+ # the RAILS_ENV to production
34
+ cron.global_setup source_bash, 'RAILS_ENV=production'
35
+
36
+ # handle some requests through rake on some app
37
+ cron.desc 'handle requests at 5 min intervals on weekdays'
38
+ cron.schedule :every => 5.minutes, :on => Days::WEEKDAYS do
39
+ rake 'app', 'requests:process'
40
+ end
41
+
42
+ # do some nightly database backups through a bash script we have
43
+ # in our scripts folder
44
+ cron.desc 'backup app database files at midnight on weekdays'
45
+ cron.schedule :at => Time::MIDNIGHT, :on => Days::WEEKDAYS do
46
+ script 'backup_database_script'
47
+ end
48
+
49
+ # clean up some old user searches if they are old
50
+ cron.desc 'clean old user searches in some app if > 1 week old on monday'
51
+ # set the DAYS_OLD environment variable so the rake task can use it
52
+ cron.schedule :at => Time::MIDNIGHT, :on => Days::MONDAY, :setup => 'DAYS_OLD=7' do
53
+ rake 'app', 'searches:clean'
54
+ end
55
+
56
+ # just a stupid 'hello world' task to show how to ignore global setups
57
+ cron.desc 'at 8:30am echo "I GOT A CASE OF THE MUNDAYS!" on mondays'
58
+ cron.schedule :at => '08:30', :on => Days::MONDAY, :ignore_global_setup => true do
59
+ "echo 'I GOT A CASE OF THE MUNDAYS!'"
60
+ end
61
+
62
+ # pointless, but just to show how to override the global setup and make your own for this one
63
+ # schedule
64
+ cron.desc 'using our OWN setup, not the global one'
65
+ cron.schedule :every => 1.hour, :on => Days::WEEKENDS, :ignore_global_setup => true, :setup => ['A=1', 'B=2'] do
66
+ "echo $A && echo $B"
67
+ end
68
+
69
+ end
70
+
71
+ # eval the CLI script so you can run it from the command line
72
+ Crontab::CLI.start! binding
@@ -1,14 +1,14 @@
1
1
  # create the cron equiv of Fixnum.minutes
2
2
  # Example:
3
- # 10.minutes #=> '10/* *'
4
- # 5.hours #=> '* 5/*'
3
+ # 10.minutes #=> '*/10 *'
4
+ # 5.hours #=> '0 */5'
5
5
  class Fixnum
6
6
  def minutes
7
- '%s/* *' % self
7
+ '*/%s *' % self
8
8
  end
9
9
  alias_method :minute, :minutes
10
10
  def hours
11
- '* %s/*' % self
11
+ '0 */%s' % self
12
12
  end
13
13
  alias_method :hour, :hours
14
14
  end
@@ -37,4 +37,12 @@ class Days
37
37
  EVERYDAY = '*'
38
38
  WEEKDAYS = '1-5'
39
39
  WEEKENDS = '0,6'
40
+ SUNDAY = 0
41
+ MONDAY = 1
42
+ TUESDAY = 2
43
+ WEDNESDAY = 3
44
+ THURSDAY = 4
45
+ FRIDAY = 5
46
+ SATURDAY = 6
47
+ SUNDAY_ALT= 7
40
48
  end
@@ -33,7 +33,7 @@
33
33
  # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
34
34
  # OTHER DEALINGS IN THE SOFTWARE.
35
35
  require 'optparse'
36
- require 'extensions'
36
+ require 'crontab_helper'
37
37
  # Example:
38
38
  # crontab = Crontab.new do |cron|
39
39
  # cron.desc 'this is my schedule description'
@@ -43,18 +43,23 @@ require 'extensions'
43
43
  #
44
44
  # see the examples directory for a more verbose example
45
45
  class Crontab
46
+
46
47
  # can either use as a block or regular object
47
48
  # Crontab.new { |cron| ... }
48
49
  # crontab = Crontab.new
49
- def initialize
50
+ def initialize(options = {})
50
51
  @schedules = []
52
+ @global_setup = nil
53
+ @header = options[:header] || false
51
54
  yield self if block_given?
52
55
  end
56
+
53
57
  # set the MAILTO user for the _entire_ crontab
54
58
  # to set it per-schedule view the schedule :mail_to arg
55
59
  def mail_to(who)
56
60
  @mail_to = who
57
61
  end
62
+
58
63
  # set the path for the _entire_ crontab
59
64
  # include_path will keep the original $PATH in there
60
65
  def path(paths, include_path = true)
@@ -63,28 +68,75 @@ class Crontab
63
68
  @paths << paths
64
69
  @paths = @paths.flatten.join(':')
65
70
  end
71
+
72
+ # defines your setup for every schedule
73
+ def global_setup(*commands)
74
+ @global_setup = commands
75
+ end
76
+
66
77
  # set your home path, probably not necessary, but why not
67
78
  def home(home_path)
68
79
  @home = home_path
69
80
  end
81
+
70
82
  # creates a schedule object from the args passes
71
83
  # your command will go in the block
72
84
  #
73
85
  # crontab.schedule(:args => ...) { "command to run" }
74
- def schedule(*args, &block)
75
- args.first.merge! :description => @last_description if @last_description
86
+ def schedule(settings, &block)
87
+ settings.merge! :description => @last_description if @last_description
88
+ setup = []
89
+ setup << @global_setup if @global_setup and not settings[:ignore_global_setup]
90
+ setup << settings[:setup] if settings[:setup]
91
+ settings[:setup] = setup.flatten.uniq.join(' && ') unless setup.empty?
76
92
  @last_description = nil
77
- @schedules << Schedule.new(*args, &block)
93
+ @schedules << Schedule.new(settings, &block)
78
94
  end
95
+
79
96
  # runs the to_cron method on all schedules and dumps
80
97
  # the crontab to be copied/pasted
81
- def to_crontab
82
- @schedules.inject(header){ |o, s| o << s.to_cron + "\n\n" }
98
+ # if display_cron_header is true, it will output the
99
+ # text block in cron_description_block
100
+ #
101
+ # FIXME: ugly as fuck
102
+ def to_crontab(display_cron_header = false)
103
+ crontab = ''
104
+ crontab << header
105
+ crontab << cron_description_block if display_cron_header
106
+ @schedules.each { |s| crontab << s.to_cron + "\n\n" }
107
+ crontab
108
+ end
109
+
110
+ # goofy cron description text to visually see whats going on if you aren't
111
+ # familiar with crontabs
112
+ # FIXME: ugly as fuck
113
+ def cron_description_block
114
+ %{
115
+ # +---------------- minute (0 - 59)
116
+ # | +------------- hour (0 - 23)
117
+ # | | +---------- day of month (1 - 31)
118
+ # | | | +------- month (1 - 12)
119
+ # | | | | +---- day of week (0 - 7) (Sunday=0 or 7)
120
+ # | | | | |
121
+ # * * * * * command to be executed
122
+
123
+ }
83
124
  end
125
+
84
126
  # sets the description for the schedule immediately following
85
127
  def desc(description)
86
128
  @last_description = description
87
129
  end
130
+
131
+ # creates a human readable list of all crob schedules
132
+ def to_s(verbose = true)
133
+ @schedules.inject('') do |o,s|
134
+ o << s.to_s + "\n"
135
+ o << "`#{s.command}`\n" if verbose
136
+ o << "\n"
137
+ end
138
+ end
139
+
88
140
  # lists the descriptions of all cron schedules for easy reviewing
89
141
  def describe
90
142
  output = "Crontabs\n" + ('-' * 40) + "\n"
@@ -94,6 +146,7 @@ class Crontab
94
146
  end
95
147
  output
96
148
  end
149
+
97
150
  def header #:nodoc:
98
151
  headers = []
99
152
  headers << "HOME=%s" % @home if @home
@@ -102,12 +155,14 @@ class Crontab
102
155
  headers << "\n" unless headers.empty?
103
156
  headers.join("\n")
104
157
  end
158
+
105
159
  # Example:
106
160
  # crontab.schedule(:at => Time::MIDNIGHT, :on => Day::WEEKDAYS) { 'backup_database' }
107
161
  # crontab.schedule(:every => 10.minutes, :on => Day::WEEKENDS) { 'check something' }
108
162
  # crontab.schedule(:at => '18:00', :on_the => '5', :of => Month::MAY) { 'party!' }
163
+ # crontab.schedule(:every => 5.minutes, :on =>Days::WEEKDAYS, :setup => 'source $HOME/.bashrc') { 'command' }
164
+ # crontab.schedule(:every => 1.hour, :on =>Days::MONDAY, :setup => ['HI=1', 'WORLD=2']) { 'command' }
109
165
  class Schedule
110
- attr_reader :description
111
166
  # args consist of
112
167
  # * :every => 1.minute, 10.minutes, 1.hour, 5.hours -- minutes or hours
113
168
  # * :at => '00:00' -- 24 hour time for when to run
@@ -115,37 +170,130 @@ class Crontab
115
170
  # * :on_the => '1,2,3' -- days of the _month_
116
171
  # * :in => Month::JANUARY -- month [0..12]
117
172
  # * :of => Month::JANUARY -- month [0..12] -- alias for readability
118
- def initialize(*args, &block)
119
- args.first.each { |k,v| instance_variable_set "@#{k}", v }
173
+ # * :setup => 'command' (or ['command', 'command', ...]) -- anything to do _before_ running your comand (source bashrc or whatnot for example)
174
+ # * :ignore_global_setup => true|false -- if you would like to ignore the global setup command
175
+ def initialize(settings, &block)
176
+ settings.each { |k,v| instance_variable_set "@#{k}", v }
120
177
  @do = yield
121
178
  end
179
+
180
+ # the command the schedule should run
181
+ def command
182
+ all_commands = []
183
+ all_commands << @setup if @setup
184
+ all_commands << @do
185
+ all_commands.join ' && '
186
+ end
187
+
122
188
  # builds the cron schedule line
123
189
  def to_cron
124
190
  cron_lines = []
125
- cron_lines << cron_description if @description
191
+ cron_lines << "# #{@description}" if @description
126
192
  cron_lines << ('MAILTO=%s' % [@mail_to]) if @mail_to
127
193
  cron_lines << ("%s %s %s %s %s" % [time, days_of_month, months, weekdays, command])
128
194
  cron_lines.join("\n")
129
195
  end
196
+
197
+ # outputs a human readable format of the cron schedule
198
+ def to_s
199
+ output = ''
200
+ output << human_time if human_time
201
+ output << ' on ' + human_day_of_month if human_day_of_month
202
+ output << ' in ' + human_month if human_month
203
+ output << ' on ' + human_weekdays if human_weekdays
204
+ output << " \"#{@description}\"" if @description
205
+ output
206
+ end
207
+
130
208
  private
131
- def cron_description #:nodoc:
132
- "# #{@description}" if @description
209
+
210
+ # converts a cron weekday list into human readable form
211
+ def human_weekdays
212
+ case weekdays
213
+ when '*': nil
214
+ when Days::WEEKDAYS: 'Weekdays'
215
+ when Days::WEEKENDS: 'Weekends'
216
+ when Days::MONDAY: 'Monday'
217
+ when Days::TUESDAY: 'Tuesday'
218
+ when Days::WEDNESDAY: 'Wednesday'
219
+ when Days::THURSDAY: 'Thursday'
220
+ when Days::FRIDAY: 'Friday'
221
+ when Days::SATURDAY: 'Saturday'
222
+ when Days::SUNDAY, Days::SUNDAY_ALT: 'Sunday'
223
+ else
224
+ weekdays
225
+ end
133
226
  end
134
- def command #:nodoc:
135
- all_commands = []
136
- all_commands << @setup if @setup
137
- all_commands << @do
138
- all_commands.join ' && '
227
+
228
+ # converts a cron day of month list into human readable form
229
+ def human_day_of_month
230
+ case days_of_month
231
+ when '*': nil
232
+ else
233
+ "the %s" % days_of_month
234
+ end
235
+ end
236
+
237
+ # converts a cron month into a human readable month
238
+ def human_month
239
+ case months
240
+ when '*': nil
241
+ when Month::JANUARY: 'January'
242
+ when Month::FEBRUARY: 'February'
243
+ when Month::MARCH: 'March'
244
+ when Month::APRIL: 'April'
245
+ when Month::MAY: 'May'
246
+ when Month::JUNE: 'June'
247
+ when Month::JULY: 'July'
248
+ when Month::AUGUST: 'August'
249
+ when Month::SEPTEMBER: 'September'
250
+ when Month::OCTOBER: 'October'
251
+ when Month::NOVEMBER: 'November'
252
+ else
253
+ months
254
+ end
255
+ end
256
+
257
+ # converts a cron minute/hour list into human readable format
258
+ def human_time
259
+ minutes, hours = time.split(' ')
260
+
261
+ minute_description = \
262
+ case minutes
263
+ when '*': 'Every minute'
264
+ when '0': nil
265
+ when /\*\/(\d*)/: "every #{$1} minutes"
266
+ end
267
+
268
+ hour_description = \
269
+ case hours
270
+ when '*': nil
271
+ when /\*\/(\d*)/: $1.to_i == 1 ? 'every hour' : "every #{$1} hours"
272
+ end
273
+
274
+ unless minute_description or hour_description
275
+ output = "at %02d:%02d" % [hours.to_i, minutes.to_i]
276
+ else
277
+ output = ''
278
+ output << minute_description if minute_description
279
+ output << ' in ' if hour_description and minute_description
280
+ output << hour_description if hour_description
281
+ end
282
+ output.capitalize
139
283
  end
284
+
140
285
  def time #:nodoc:
141
286
  @at ? (@at || '*:*').split(':').reverse.join(' ') : (@every || '* *')
142
287
  end
288
+
143
289
  def days_of_month #:nodoc:
144
290
  @on_the || '*'
145
291
  end
292
+
146
293
  def months #:nodoc:
147
294
  @in || @of || '*'
148
295
  end
296
+
149
297
  def weekdays #:nodoc:
150
298
  @on || '*'
151
299
  end
@@ -153,14 +301,17 @@ class Crontab
153
301
  # Simple CLI class which includes basic command line options
154
302
  # to the cron script
155
303
  # Options:
156
- # -h --help print the usage
157
- # -d --describe describes the crontab
304
+ # -h --help print the usage
305
+ # --cron-description shows the cron description block
306
+ # -d --describe describes the crontab
158
307
  class CLI
159
308
  # evaluates the necessary script to handle CLI option parsing
160
309
  # it wants the current binding so inside your script you'd do
161
310
  # Example:
162
311
  # Crontab::CLI.start!(binding)
163
- def self.start!(the_binding)
312
+ # if your crontab is _not_ named 'crontab' you must pass the object in using
313
+ # Crontab::CLI.start! binding, whatever_your_crontab_object_is_called
314
+ def self.start!(binding, crontab_object = 'crontab')
164
315
  eval %{if $0 == __FILE__
165
316
  options = {}
166
317
  OptionParser.new do |opts|
@@ -168,6 +319,9 @@ class Crontab
168
319
  opts.on('-d', '--describe', 'Describe current cron schedules') do |d|
169
320
  options[:describe] = d
170
321
  end
322
+ opts.on('--cron-description', 'Show the cron description block in the crontab') do |c|
323
+ options[:cron_description] = c
324
+ end
171
325
  opts.on_tail("-h", "--help", "Show this message") do
172
326
  puts opts
173
327
  exit
@@ -175,12 +329,12 @@ class Crontab
175
329
  end.parse!
176
330
 
177
331
  if options[:describe]
178
- puts crontab.describe
332
+ puts #{crontab_object}.to_s
179
333
  else
180
- puts crontab.to_crontab
334
+ puts #{crontab_object}.to_crontab(options[:cron_description])
181
335
  end
182
336
  end
183
- }, the_binding
337
+ }, binding
184
338
  end
185
339
  end
186
340
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbcrontab
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.1"
4
+ version: "1.0"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Hurring
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-24 00:00:00 -04:00
12
+ date: 2008-09-22 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -23,7 +23,8 @@ extra_rdoc_files:
23
23
  - README
24
24
  files:
25
25
  - examples/cli_crontab.rb
26
- - lib/extensions.rb
26
+ - examples/full_example.rb
27
+ - lib/crontab_helper.rb
27
28
  - lib/rbcrontab.rb
28
29
  - README
29
30
  - Rakefile