cronedit 0.2.0 → 0.3.0

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.
@@ -3,9 +3,15 @@
3
3
  #
4
4
  # Allows to manipulate crontab from comfortably from ruby code.
5
5
  # You can add/modify/remove (aka CRUD) named crontab entries individually with no effect on the rest of your crontab.
6
- # You can define cron entry definitions as standart text definitions <tt>'10 * * * * echo 42'</tt> or using Hash notation <tt>{:minute=>10, :command=>'echo 42'}</tt> (see CronEntry ::DEFAULTS)
7
- # Additionaly you can parse cron text definitions to Hash.
6
+ # You can define cron entry definitions as standard text definitions <tt>'10 * * * * echo 42'</tt> or using Hash notation <tt>{:minute=>10, :command=>'echo 42'}</tt> (see CronEntry ::DEFAULTS)
7
+ # Additionally you can parse cron text definitions to Hash.
8
+ #
9
+ # From other features: CronEdit allows you to make bulk updates of crontab; the same way you manipulate live crontab you can edit file or in-memory definitions and combine them arbitrarily.
8
10
  #
11
+ # ==Install
12
+ # * Available as gem: <tt>gem install cronedit</tt>
13
+ # * Project development page, downloads, forum, svn: http://rubyforge.org/projects/cronedit/
14
+ #
9
15
  # ==Usage
10
16
  #
11
17
  # Class methods offer quick crontab operations. Three examples:
@@ -13,55 +19,88 @@
13
19
  # CronEdit::Crontab.Add 'agent2', {:minute=>5, :command=>'echo 42'}
14
20
  # CronEdit::Crontab.Remove 'someId'
15
21
  #
16
- # or define a batch update and list the current content:
22
+ # Define a batch update and list the current content:
17
23
  #
18
- # cm = CronEdit:Crontab.new 'user'
24
+ # cm = CronEdit::Crontab.new 'user'
19
25
  # cm.add 'agent1', '5,35 0-23/2 * * * echo agent1'
20
26
  # ...
21
27
  # cm.add 'agent2', {:minute=>5, :command=>'echo 42'}
22
28
  # cm.commit
23
29
  # p cm.list
30
+ #
31
+ # see CronEdit::Crontab class for all available methods
32
+ #
33
+ # You can do a bulk merge (or removal) of definitions from a file using CronEdit::FileCrontab
34
+ # fc = FileCrontab.new 'example1.cron'
35
+ # Crontab.Merge fc
36
+ # p Crontab.List
37
+ # Crontab.Subtract fc
38
+ #
39
+ # Similary to CronEdit::FileCrontab you can you also CronEdit::DummyCrontab for in-memory crontabs.
40
+ # Above all, you can combine all three crontab implementations Crontab, FileCrontab, DummyCrontab arbitrarily.
41
+ #
42
+ # see <tt>test/examples/examples.rb</tt> for more examples !
24
43
  #
25
- # see Crontab for all available methods
26
44
  # ==Author
27
- # Viktor Zigo, http://7inf.com, All rights reserved. You can redistribute it and/or modify it under the same terms as Ruby.
28
- # (parts of the cron definition parsing code originaly by gotoken@notwork.org)
45
+ # Viktor Zigo, http://alephzarro.com, All rights reserved. You can redistribute it and/or modify it under the same terms as Ruby.
46
+ # (parts of the cronentry definition parsing code originally by gotoken@notwork.org)
47
+ #
48
+ # Sponsored by: http://7inf.com
29
49
  # ==History
30
- # version: 0.2.0
50
+ # * version: 0.3.0 2008-02-02
51
+ # ** keeps/survives full fromatting; FileCrontab; DummyCrontab; bulk addition and removal; clear crontab; more testcases; examples; and other
52
+ # * version: 0.2.0
31
53
  #
32
54
  # ==TODO
33
- # add more commands (clean crontab); lift some methods to class; put tests to separate file; make rake, make gem; implement addAllfrom
34
- # add Utils: getNext execution
55
+ # * add Utils: getNext execution
56
+ # * platform specific options (headers; vixiecron vs. dilloncron)
35
57
  module CronEdit
36
- self::VERSION = '0.2.0' unless defined? self::VERSION
37
-
58
+ self::VERSION = '0.3.0' unless defined? self::VERSION
59
+
60
+ # Main class that manipulates actual system cron. Additionally, it is the base class for other types of Crontab utils (FileCrontab, DummyCrontab)
38
61
  class Crontab
39
62
  # Use crontab for user aUser
40
63
  def initialize aUser = nil
41
64
  @user = aUser
65
+ @opts = {:close_input=>true, :close_output=>true}
42
66
  rollback()
43
67
  end
44
68
 
45
69
  class <<self
46
- # Add a new crontab entry definition. See instance add method
70
+ # Add a new crontab entry definition. (see instance method add).
47
71
  def Add anId, aDef
48
- cm = Crontab.new
72
+ cm = self.new
49
73
  entry = cm.add anId, aDef
50
74
  cm.commit
51
75
  entry
52
76
  end
53
77
 
54
- # Remove a crontab entry definition identified by anId from current crontab.
55
- def Remove anId
56
- cm = Crontab.new
57
- cm.remove anId
78
+ # Remove a crontab entry definitions identified by anIds from current crontab (see instance method remove).
79
+ def Remove *anIds
80
+ cm = self.new
81
+ cm.remove *anIds
58
82
  cm.commit
59
83
  end
60
84
 
61
85
  # List current crontab.
62
86
  def List
63
- Crontab.new.list
64
- end
87
+ self.new.list
88
+ end
89
+
90
+ #Merges this crontab with another one (see instance method merge).
91
+ def Merge anotherCrontab
92
+ cm=self.new
93
+ cm.merge anotherCrontab
94
+ cm.commit
95
+ end
96
+
97
+ #Removes crontab definitions of another crontab (see instance method subtract).
98
+ def Subtract anotherCrontab
99
+ cm=self.new
100
+ cm.subtract anotherCrontab
101
+ cm.commit
102
+ end
103
+
65
104
  end
66
105
 
67
106
  # Add a new crontab entry definition. Becomes effective only after commit().
@@ -70,51 +109,75 @@ module CronEdit
70
109
  # returns newly added CronEntry
71
110
  def add anId, aDef
72
111
  @adds[anId.to_s] = CronEntry.new( aDef )
112
+ @removals.delete anId.to_s
73
113
  end
74
114
 
75
- # Bulk addition of crontab entry definitions from stream (String, File, IO)
76
- def addAllFrom anIO
77
- #TODO: load file, parse it
78
- raise 'Not implemented yet'
115
+ # Bulk addition/merging of crontab definitions from another Crontab (FileCrontab, DummyCrontab, or a Crontab of another user)
116
+ def merge aCrontab
117
+ entries, lines = aCrontab.listFull
118
+ #todo: we throw the incoming lines away
119
+ #merge original data
120
+ @adds.merge! entries
121
+ #merge noncommited data as well
122
+ aCrontab.adds.each {|k,v| @adds[k]=v}
123
+ aCrontab.removals.each {|k,v| remove k}
124
+ self
79
125
  end
80
126
 
81
- # Remove a crontab entry definition identified by anId. Becomes effective only after commit().
82
- def remove anId
83
- @adds.delete anId.to_s
84
- @removals[anId.to_s]=anId.to_s
127
+ # Bulk subtraction/removal of crontab definitions from another Crontab (FileCrontab, DummyCrontab, or a Crontab of another user)
128
+ def subtract aCrontab
129
+ entries, lines = aCrontab.listFull
130
+ entries.each {|id,entry| remove id}
131
+ aCrontab.adds.each {|id,entry| remove id}
85
132
  end
86
133
 
134
+ # Remove a crontab entry definitions identified by anIds. Becomes effective only after commit().
135
+ def remove *anIds
136
+ anIds.each { |id|
137
+ @adds.delete id.to_s
138
+ @removals[id.to_s]=id.to_s
139
+ }
140
+ end
141
+
87
142
  # Merges the existing crontab with all modifications and installs the new crontab.
88
143
  # returns the merged parsed crontab hash
89
144
  def commit
90
145
  # merge crontab
91
- current = list()
92
- current.delete_if {|id, entry| @removals.include? id}
93
- merged = current.merge @adds
94
-
146
+ currentEntries, lines = listFull()
147
+ mergedEntries = nil
95
148
  # install it
96
- cmd = @user ? "crontab -u #{@user} -" : "crontab -"
97
- IO.popen(cmd,'w') {|io|
98
- merged.each {|id, entry|
99
- io.puts "# #{id}"
100
- io.puts entry
101
- }
102
- io.close
103
- }
149
+ io = getOutput
150
+ begin
151
+ mergedEntries = dumpCron currentEntries, lines, io
152
+ ensure
153
+ io.close if @opts[:close_output]
154
+ end
104
155
  # No idea why but without this any wait crontab reads and writes appears not synchronizes
105
156
  sleep 0.01
106
157
  #clean changes :)
107
158
  rollback()
108
- merged
159
+ mergedEntries
109
160
  end
110
-
161
+
111
162
  # Discards all modifications (since last commit, or creation)
112
163
  def rollback
113
164
  @adds = {}
114
165
  @removals = {}
115
166
  end
116
167
 
117
- # Prints out the items to be added and removed
168
+ # Clear crontab completely and immediately. Warning: no commit needed (no rollback anymore)
169
+ def clear!
170
+ rollback
171
+ # we dont do it using crontab command (-r , -d) because it differs on various platfoms (vixiecron vs. dilloncron)
172
+ io = getOutput
173
+ begin
174
+ io << ''
175
+ ensure
176
+ io.close if @opts[:close_output]
177
+ end
178
+ end
179
+
180
+ # A helper method that prints out the items to be added and removed
118
181
  def review
119
182
  puts "To be added: #{@adds.inspect}"
120
183
  puts "To be removed: #{@removals.keys.inspect}"
@@ -123,43 +186,225 @@ module CronEdit
123
186
  # Read the current crontab and parse it
124
187
  # returns a Hash (entry id or index)=>CronEntry
125
188
  def list
126
- cmd = @user ? "crontab -u #{@user} -l" : "crontab -l"
127
- IO.popen(cmd) {|io|
128
- return parseCrontab(io)
129
- }
189
+ res = listFull
190
+ res ? res[0] : nil
130
191
  end
131
192
 
193
+ # Read the current crontab and parse it, keeping the format
194
+ # returns a [entires, lines] where entries = Hash (entry id or index)=>CronEntry; lines = Array of Strings of EntryPlaceholders
195
+ def listFull
196
+ io = getInput
197
+ begin
198
+ return parseCrontabFull(io)
199
+ ensure
200
+ io.close if @opts[:close_input]
201
+ end
202
+ end
203
+
132
204
  # Lists raw content from crontab
133
205
  # returns array of text lines
134
206
  def listRaw
135
- cmd = @user ? "crontab -u #{@user} -l" : "crontab -l"
136
- IO.popen(cmd) {|io|
137
- entries = io.readlines
138
- return (entries.first =~ /^no/).nil? ? entries : []
139
- }
207
+ io = getInput
208
+ begin
209
+ entries = io.readlines
210
+ return (entries.first =~ /^no/).nil? ? entries : [] #returns empty list if no entry
211
+ ensure
212
+ io.close if @opts[:close_input]
213
+ end
140
214
  end
141
-
215
+
216
+ #Set alternative I/O for reading/writing cron entries. If set, the respective stream will be used instead of calling system 'crontab'
217
+ def setIO anInput, anOutput
218
+ @input = anInput
219
+ @output = anOutput
220
+ self
221
+ end
222
+ protected
223
+ # Parses cron definition from a stream
224
+ # returns Hash of id->CrontEntries
142
225
  def parseCrontab anIO
226
+ res = parseCrontabFull(anIO)
227
+ res ? res[0] : nil
228
+ end
229
+
230
+ # Parses cron definition from a stream
231
+ # returns [hash of CronEntries, Array of all lines (and placeholders)]
232
+ def parseCrontabFull anIO
233
+ lines=[]
143
234
  entries = {}
144
235
  idx = 0
145
236
  id = nil
146
237
  anIO.each_line { |l|
147
238
  l.strip!
239
+ lines << l if l.empty? # keep empty lines and opts[:keepformat]
148
240
  next if l.empty?
149
- return {} unless (l =~ /^no/).nil?
150
- if l=~/^#/
151
- id = l[1, l.size-1].strip
152
- else
153
- key = id.nil? ? (idx+=1) : id
241
+ return [entries, lines] unless (l =~ /^no crontab/).nil? # if crontab contains no schedule it returns 'no crontab for ...'
242
+
243
+ if l=~/^#/ #is it comment or ID ?
244
+ id = EntryPlaceholder.Create l
245
+ lines << l unless id #add the raw line to the lines if no id
246
+ elsif not (l =~ /^\w+\ *=/).nil? # is it variable definition ?
247
+ lines << l
248
+ id = nil
249
+ else # it should be cron entry definition
250
+ id = EntryPlaceholder.new(idx+=1) unless id # if there is no id generate an anonymous id
251
+ lines << id #add placeholder to lines
252
+ key = id.id
154
253
  entries[key.to_s]=l
155
254
  id = nil
156
255
  end
157
256
  }
158
- entries
257
+ [entries, lines]
159
258
  end
259
+
260
+ private
261
+ def dumpCron anEntries, aLines, anIO
262
+ currentEntries=anEntries.clone
263
+ currentEntries.delete_if {|id, entry| @removals.include? id}
264
+ mergedEntries = currentEntries.merge @adds
265
+
266
+ usedIds=[]
267
+ # dump lines and placeholders
268
+ aLines.each { |line|
269
+ if line.respond_to? :mergedump
270
+ usedIds << line.mergedump( anIO, mergedEntries )
271
+ else
272
+ anIO.puts line.to_s
273
+ end
274
+ }
275
+ #dump new entries
276
+ restIds = mergedEntries.keys - usedIds
277
+ restLines = restIds.map {|id| EntryPlaceholder.new id}
278
+ restLines .each {|line| line.mergedump anIO, mergedEntries }
279
+ mergedEntries
280
+ end
281
+
282
+ protected
283
+ # override this if you want to get the cron definition from other sources
284
+ def getInput
285
+ if @input
286
+ @input
287
+ else
288
+ cmd = @user ? "crontab -u #{@user} -l" : "crontab -l"
289
+ IO.popen(cmd)
290
+ end
291
+ end
292
+
293
+ # override this if you want to write the cron definition from other destination
294
+ def getOutput
295
+ if @output
296
+ @output
297
+ else
298
+ cmd = @user ? "crontab -u #{@user} -" : "crontab -"
299
+ IO.popen(cmd,'w')
300
+ end
301
+ end
302
+
303
+ attr_reader :adds, :removals
160
304
  end
161
305
 
306
+ # FileCrontab allows you to read in/write out crontab definitions from/to files. You might want to use it for merging some enire file to crontab.
307
+ # If '-' is given instead of input file / output file the definitions are streamed from/into STDIN/STDOUT.
308
+ #
309
+ # Example:
310
+ # fc = FileCrontab.new 'crontemplate.txt', '/tmp/crontest.txt'
311
+ # fc.add 'agent', '59 * * * * echo "modified"'
312
+ # fc.remove 'agent2'
313
+ # fc.commit
314
+ #
315
+ # or create a crontab fron scratch and output to STDOUT
316
+ # fc = FileCrontab.new nil, '-'
317
+ # fc.add 'agent', '59 * * * * echo "modified"'
318
+ # fc.commit
319
+
320
+
321
+ class FileCrontab < Crontab
322
+ # anInputFile and anOutputFile can be either filename - to r/w the file, '-' to use STDIN/STDOUT, or nil for no input/output
323
+ def initialize anInputFile = nil, anOutputFile = nil
324
+ super nil
325
+ @input = anInputFile
326
+ @opts[:close_input] = false if @input=='-'
327
+ @output = anOutputFile
328
+ @opts[:close_output] = false if @output=='-'
329
+ end
330
+ protected
331
+ def getInput
332
+ case @input
333
+ when nil then StringIO.new
334
+ when '-' then STDIN
335
+ else File.open( @input, 'r' )
336
+ end
337
+ end
338
+
339
+ def getOutput
340
+ case @output
341
+ when nil then StringIO.new
342
+ when '-' then STDOUT
343
+ else File.open( @output, 'w' )
344
+ end
345
+ end
346
+ end
347
+
348
+ # DummyCrontab allows you to create crontab definitions from scratch and dump them out as string. (Or combine them with other Crontab definitions)
349
+ #
350
+ # Example:
351
+ # fc = DummyCrontab.new
352
+ # fc.add 'agent1', '59 * * * * echo "agent1"'
353
+ # fc.add 'agent2', {:hour=>'2',:command=>'echo "huh"'}
354
+ # fc.commit
355
+ # puts fc
356
+ class DummyCrontab < Crontab
357
+ require 'stringio'
358
+ def initialize
359
+ super nil
360
+ @buffer = StringIO.new
361
+ end
362
+
363
+ def to_s
364
+ @buffer.string
365
+ end
366
+
367
+ protected
368
+ def getInput
369
+ #loopback - for multiple use
370
+ StringIO.new @buffer.string
371
+ end
372
+
373
+ def getOutput
374
+ @buffer = StringIO.new
375
+ end
376
+ end
377
+ # When the Crontab object reads in (parse) all lines, placeholder appears where a Crontab entry with an id was detected
378
+ class EntryPlaceholder
379
+ attr_reader :id
380
+ def self.Create(aLine)
381
+ id = ScanId aLine
382
+ return id ? EntryPlaceholder.new(id) : nil
383
+ end
384
+ def self.ScanId aStr
385
+ aStr.scan(/^\#\#__(.*)__/).flatten.first
386
+ end
387
+ def self.GenerateId anId
388
+ "##__#{anId}__"
389
+ end
390
+ def initialize anId
391
+ @id = anId.to_s
392
+ end
393
+
394
+ #returns used id
395
+ def mergedump anIO, anEntryHash
396
+ entry = anEntryHash[@id]
397
+ if entry
398
+ anIO.puts "#{EntryPlaceholder.GenerateId @id}\n"
399
+ anIO.puts "#{entry}\n"
400
+ return @id
401
+ else
402
+ return nil
403
+ end
404
+ end
405
+ end
162
406
 
407
+ #A cron entry ...
163
408
  class CronEntry
164
409
  DEFAULTS = {
165
410
  :minute => '*',
@@ -262,73 +507,4 @@ end
262
507
 
263
508
  if __FILE__ == $0
264
509
  include CronEdit
265
-
266
-
267
-
268
-
269
- ents = [
270
- CronEntry.new( "5,35 0-23/2 * * * echo 123" ),
271
- CronEntry.new,
272
- CronEntry.new( {:minute=>5, :command=>'echo 42'} ),
273
- ]
274
- ents.each { |e| puts e }
275
-
276
- e= CronEntry.new( "5,35 0-23/2 * * * echo 123" )
277
- puts "Minute: #{e[:minute].inspect}"
278
- puts "Hour: #{e[:hour].inspect}"
279
- puts "Day: #{e[:day].inspect}"
280
- p e.to_s
281
- p e.to_hash
282
- p e.to_raw
283
-
284
- begin
285
- CronEntry.new( {:minuteZ=>5, :command=>'echo 42'} )
286
- puts "Failure failed"
287
- rescue
288
- end
289
-
290
- begin
291
- CronEntry.new( "1-85 2 * * * echo 123" )
292
- puts "Failure failed"
293
- rescue CronEntry::FormatError
294
- end
295
-
296
- crontabTest=%Q{
297
- 5,35 0-23/2 * * * echo 123
298
- #agent1
299
- 3 * * * * echo agent1
300
-
301
-
302
- #agent2
303
- 3 * * * * echo agent2
304
- #ignored comment
305
- #agent1
306
- 3 * * * * echo agent3
307
- }
308
- puts "Crontab raw: #{Crontab.new.listRaw.inspect}"
309
- puts "Crontab: #{Crontab.new.list.inspect}"
310
- puts "Crontab test: #{Crontab.new.parseCrontab(crontabTest).inspect }"
311
-
312
- #rollback test
313
- cm = Crontab.new
314
- cm. add 'agent1', '5,35 0-23/2 * * * "echo 123" '
315
- cm.remove "agent2"
316
- cm.review
317
- cm.rollback
318
-
319
- #commit test
320
- cm = Crontab.new
321
- cm. add "agent1", "5,35 0-23/2 * * * echo agent1"
322
- cm. add "agent2", "0 2 * * * echo agent2"
323
- cm.commit
324
- current=cm.list
325
- puts "New crontab 1: #{current.inspect}"
326
-
327
- cm = Crontab.new
328
- cm. add "agent1", '59 * * * * echo "modified agent1"'
329
- cm.remove "agent2"
330
- cm.commit
331
- current=Crontab.List
332
- puts "New crontab 2: #{current.inspect}"
333
- raise "Assertion" unless current=={'agent1'=> '59 * * * * echo "modified agent1"'}
334
510
  end
@@ -2,14 +2,37 @@ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
2
 
3
3
  require 'test/unit'
4
4
  require 'cronedit.rb'
5
+ require 'stringio'
5
6
  include CronEdit
6
7
 
8
+ class Class
9
+ def publicize_methods
10
+ saved_private_instance_methods = self.private_instance_methods
11
+ saved_protected_instance_methods = self.protected_instance_methods
12
+ self.class_eval { public(*saved_private_instance_methods) }
13
+ self.class_eval { public(*saved_protected_instance_methods) }
14
+ yield
15
+ self.class_eval { protected(*saved_protected_instance_methods) }
16
+ self.class_eval { private(*saved_private_instance_methods) }
17
+ end
18
+ end
19
+
7
20
  class CronEdit_test < Test::Unit::TestCase
8
- # def setup
9
- # end
10
- #
11
- # def teardown
12
- # end
21
+ def setup
22
+ #backup crontab
23
+ `crontab -l > /tmp/crontab.bak`
24
+ end
25
+
26
+ def teardown
27
+ #restore crontab
28
+ `crontab /tmp/crontab.bak`
29
+ end
30
+
31
+ def test_idParsing
32
+ idin=['##__id1__', ' ##__id3__', '#the end comment','kuk','##___id4___','###__id5__']
33
+ out = idin.map {|l| CronEdit::EntryPlaceholder.ScanId(l)}
34
+ assert_equal(['id1',nil,nil,nil,'_id4_',nil], out, "idParsing")
35
+ end
13
36
 
14
37
  def test_creation
15
38
  e = CronEntry.new( "5,35 0-23/2 * * * echo 123" )
@@ -29,11 +52,12 @@ class CronEdit_test < Test::Unit::TestCase
29
52
  assert_equal( "0,2,4,6,8,10,12,14,16,18,20,22", e[:hour])
30
53
  assert_equal( "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", e[:day])
31
54
  end
32
- def test_wrongformat
55
+
56
+ def test_wrongformat
33
57
  assert_raise(CronEntry::FormatError){
34
58
  CronEntry.new( "1-85 2 * * * echo 123" )
35
59
  }
36
- end
60
+ end
37
61
 
38
62
  def test_wrongconfog
39
63
  assert_raise(RuntimeError){
@@ -42,29 +66,39 @@ class CronEdit_test < Test::Unit::TestCase
42
66
  end
43
67
 
44
68
  def test_zip
45
- crontabTest=%Q{
46
- 5,35 0-23/2 * * * echo 123
47
- #agent1
48
- 3 * * * * echo agent1
69
+ Crontab.publicize_methods {
70
+ crontabTest=%Q{
71
+ 5,35 0-23/2 * * * echo 123
72
+ ##__agent1__
73
+ 3 * * * * echo agent1
49
74
 
50
75
 
51
- #agent2
52
- 3 * * * * echo agent2
53
- #ignored comment
54
- #agent1
55
- 3 * * * * echo agent3
56
- }
57
- expected = {"agent1"=>"3 * * * * echo agent3", "agent2"=>"3 * * * * echo agent2", "1"=>"5,35 0-23/2 * * * echo 123"}
58
- assert_equal( expected, Crontab.new.parseCrontab(crontabTest), 'parsing of crontab file')
76
+ ##__agent2__
77
+ 3 * * * * echo agent2
78
+ #ignored comment
79
+ ##__agent1__
80
+ 3 * * * * echo agent3
81
+ }
82
+ expected = {"agent1"=>"3 * * * * echo agent3", "agent2"=>"3 * * * * echo agent2", "1"=>"5,35 0-23/2 * * * echo 123"}
83
+ assert_equal( expected, Crontab.new.parseCrontab(crontabTest), 'parsing of crontab file')
84
+ }
59
85
  end
60
86
 
61
87
  def test_emptycrontab
62
- assert_equal( [], Crontab.new.listRaw )
63
- assert_equal( {}, Crontab.new.list )
88
+ input = 'no crontab for user'
89
+ assert_equal( {}, Crontab.new.setIO(StringIO.new(input),nil).list )
90
+ end
91
+
92
+ def test_clear
93
+ Crontab.new.clear!
94
+ entries, lines = Crontab.new.listFull
95
+ assert_equal( {}, entries, "Entries" )
96
+ Crontab.new.list
64
97
  end
65
98
 
66
99
  def test_rollback
67
100
  #rollback test
101
+ `crontab -r`; `crontab -d`
68
102
  assert_equal( {}, Crontab.new.list, 'precondition' )
69
103
  cm = Crontab.new
70
104
  cm. add 'agent1', '5,35 0-23/2 * * * "echo 123" '
@@ -74,7 +108,68 @@ class CronEdit_test < Test::Unit::TestCase
74
108
  assert_equal( {}, Crontab.new.list )
75
109
  end
76
110
 
111
+ def test_complex
112
+ Crontab.publicize_methods {
113
+ File.open( File.join(File.dirname(__FILE__), 'testcron.txt') ) { |f|
114
+ content = f.read
115
+ f.rewind
116
+ entries, lines = Crontab.new.parseCrontabFull f
117
+ assert_equal 12, lines.length, "Lines"
118
+ assert_equal ['1','2','3','test'].sort, entries.keys.sort, "Entries"
119
+ #puts "--------------Lines: \n#{lines.join("\n") }"
120
+ #puts "--------------entries: \n#{entries.map {|k,v| "#{k}=>#{v}\n"} }"
121
+ output = StringIO.new
122
+ Crontab.new.dumpCron entries, lines, output
123
+ #puts "OUT: #{output.string}"
124
+ #loop
125
+ output.rewind
126
+ entries2, lines2 = Crontab.new.parseCrontabFull output
127
+ assert_equal 12, lines2.length, "Lines2"
128
+ assert_equal ['1','2','3','test'].sort, entries2.keys.sort, "Entries2"
129
+ }
130
+ }
131
+ end
132
+
133
+ def test_complex2
134
+ crontabTest=%Q{
135
+ MAIL = user
136
+ ##__agent1__
137
+ 3 * * * * echo agent1
138
+
139
+
140
+ #comment
141
+ 3 * * * * echo agent2
142
+
143
+ ##__blankId__
144
+ #anonymous
145
+ 3 * * * * echo anonymous
146
+ }
147
+ ct = Crontab.new
148
+ ct.setIO StringIO.new(crontabTest), nil
149
+ entries, lines = ct.listFull
150
+ assert_equal 11, lines.length, "Lines"
151
+ assert_equal ['agent1','1','2'].sort, entries.keys.sort, "Entries"
152
+ ## puts "--------------Lines: \n#{lines.join("\n") }"
153
+ ## puts "--------------entries: \n#{entries.map {|k,v| "#{k}=>#{v}\n"} }"
154
+
155
+ ct.add 'agent1', '5,35 0-23/2 * * * echo agent1' #overwriting
156
+ ct.add 'agent3', '0 2 * * * echo agent3' #new agent
157
+ ct.remove '2'
158
+ output = StringIO.new
159
+ ct.setIO StringIO.new(crontabTest), output
160
+ ct.commit
161
+ ## puts '-'*40 + "\n#{output.string}\n" + '-'*40
162
+ # loop
163
+ ct.setIO StringIO.new(output.string), nil
164
+ entries, lines = ct.listFull
165
+ assert_equal 11, lines.length, "Lines"
166
+ assert_equal ['agent1','agent3','1'].sort, entries.keys.sort, "Entries"
167
+ ## puts "--------------Lines: \n#{lines.join("\n") }"
168
+ ## puts "--------------entries: \n#{entries.map {|k,v| "#{k}=>#{v}\n"} }"
169
+ end
170
+
77
171
  def test_commit
172
+ `crontab -r`; `crontab -d`
78
173
  assert_equal( {}, Crontab.new.list, 'precondition' )
79
174
  cm = Crontab.new
80
175
  cm. add "agent1", "5,35 0-23/2 * * * echo agent1"
@@ -96,4 +191,105 @@ class CronEdit_test < Test::Unit::TestCase
96
191
  assert_equal( {}, Crontab.List, 'precondition' )
97
192
  end
98
193
 
194
+ def test_classMethods
195
+ Crontab.List
196
+ end
197
+
198
+ def test_filecrontab
199
+ fc = FileCrontab.new File.join(File.dirname(__FILE__), 'testcron.txt'), '/tmp/crontest.txt'
200
+ fc.add 'file', '59 * * * * echo "file"'
201
+ fc.remove 2, 3
202
+ fc.commit
203
+ # check it
204
+ entries, lines = FileCrontab.new( '/tmp/crontest.txt',nil).listFull
205
+ assert_equal 11, lines.length, "Lines"
206
+ assert_equal ['1','test','file'].sort, entries.keys.sort, "Entries"
207
+ end
208
+
209
+ def test_filecrontab2
210
+ fc = FileCrontab.new nil, '-'
211
+ fc.add 'agent', '59 * * * * echo 123'
212
+ fc.commit
213
+ #todo: how to test STDOUT
214
+
215
+ fc = FileCrontab.new nil, '/tmp/crontest.txt'
216
+ fc.add 'agent', '59 * * * * echo 123'
217
+ fc.commit
218
+ File.open( '/tmp/crontest.txt') {
219
+ |f|
220
+ assert_equal f.read, "##__agent__\n59 * * * * echo 123\n"
221
+ }
222
+ end
223
+
224
+ def test_dummycrontab
225
+ fc = DummyCrontab.new
226
+ fc.add 'agent1', '59 * * * * echo "agent1"'
227
+ fc.add 'agent2', {:hour=>'2',:command=>'echo "huh"'}
228
+ fc.commit
229
+ #check 1
230
+ output = fc.to_s
231
+ entries, lines = Crontab.new.setIO(StringIO.new(output),nil).listFull
232
+ assert_equal 2, lines.length, "Lines"
233
+ assert_equal ['agent1','agent2'].sort, entries.keys.sort, "Entries"
234
+ #continue editing
235
+ fc.remove 'agent2'
236
+ fc.commit
237
+ #check 2
238
+ output = fc.to_s
239
+ entries, lines = Crontab.new.setIO(StringIO.new(output),nil).listFull
240
+ assert_equal 1, lines.length, "Lines"
241
+ assert_equal ['agent1'].sort, entries.keys.sort, "Entries"
242
+ end
243
+
244
+ def test_merge
245
+ fc1 = DummyCrontab.new
246
+ fc1.add 'agent1', '59 * * * * echo "agent1"'
247
+ fc1.add 'agent2', {:hour=>'2',:command=>'echo "huh"'}
248
+ fc1.commit
249
+
250
+ fc2 = DummyCrontab.new
251
+ fc2.add 'agent3', '59 * * * * echo "agent3"'
252
+ fc2.remove 'agent2'
253
+
254
+ fc1.merge fc2
255
+ fc1.commit
256
+ output = fc1.to_s
257
+ entries, lines = Crontab.new.setIO(StringIO.new(output),nil).listFull
258
+ assert_equal 2, lines.length, "Lines"
259
+ assert_equal ['agent1','agent3'].sort, entries.keys.sort, "Entries"
260
+ end
261
+
262
+ def test_mergeWithFile
263
+ fc1 = FileCrontab.new File.join(File.dirname(__FILE__), 'testcron.txt')
264
+
265
+ fc2 = DummyCrontab.new
266
+ fc2.add 'agent3', '59 * * * * echo "agent3"'
267
+
268
+ fc2.merge fc1
269
+ fc2.commit
270
+ output = fc2.to_s
271
+ ## puts "\n\n"+output
272
+ entries, lines = Crontab.new.setIO(StringIO.new(output),nil).listFull
273
+ assert_equal 5, lines.length, "Lines"
274
+ assert_equal ['1','2','3','test','agent3'].sort, entries.keys.sort, "Entries"
275
+ end
276
+
277
+ def test_subtractFile
278
+ fc1 = DummyCrontab.new
279
+ fc1.add 'test', '59 * * * * echo "whatever"'
280
+ fc1.add 'test2', '59 * * * * echo "whatever"'
281
+ fc1.add '3', '59 * * * * echo "whatever"'
282
+ fc1.commit
283
+
284
+
285
+ fc2= FileCrontab.new File.join(File.dirname(__FILE__), 'testcron.txt')
286
+ fc1.subtract fc2
287
+ fc1.commit
288
+ output = fc1.to_s
289
+ ## puts "\n\n"+output
290
+ entries, lines = Crontab.new.setIO(StringIO.new(output),nil).listFull
291
+ assert_equal 1, lines.length, "Lines"
292
+ assert_equal ['test2'].sort, entries.keys.sort, "Entries"
293
+ end
294
+
99
295
  end
@@ -0,0 +1,4 @@
1
+ ##__echo1__
2
+ * * * * * echo 1 >> /tmp/echo.log
3
+ ##__echo2__
4
+ * * * * * echo 2 >> /tmp/echo.log
@@ -0,0 +1,51 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'../..','lib')
2
+ require 'cronedit.rb'
3
+ include CronEdit
4
+
5
+ # 1. Direct cron modifications
6
+ Crontab.Add 'agent1', '5,35 0-23/2 * * * echo agent1'
7
+ Crontab.Add 'agent2', {:minute=>5, :command=>'echo 42'}
8
+ p Crontab.List
9
+ Crontab.Remove 'agent1', 'agent2'
10
+
11
+ # 2. Batch modification
12
+ cm = Crontab.new
13
+ cm.add :agent1, '5,35 0-23/2 * * * echo "agent1" >> /tmp/agent/log'
14
+ cm.add :agent2, {:minute=>5, :command=>'echo 42'}
15
+ cm.commit
16
+ p cm.list
17
+
18
+
19
+ #3. Delete crontab completely
20
+ cm.clear!
21
+
22
+ # 4. Bulk merge from file using FileCrontab
23
+ fc = FileCrontab.new File.join(File.dirname(__FILE__), 'example1.cron')
24
+ # Add all entries from file
25
+ Crontab.Merge fc
26
+ p Crontab.List
27
+ # An an entry manually
28
+ Crontab.Add :test, '* * * * * test'
29
+ # Remove all entries defined in the file
30
+ Crontab.Subtract fc
31
+ p Crontab.List
32
+
33
+ # 5. Read from file - output to STDOUT (you can use a file instead of STDOUT)
34
+ fc = FileCrontab.new File.join(File.dirname(__FILE__), 'example1.cron'), '-'
35
+ fc.add :agent2, {:minute=>5, :command=>'echo 42'}
36
+ fc.commit
37
+
38
+
39
+ # 6. DummyCrontab - in memory crontab
40
+ dc = DummyCrontab.new
41
+ dc.add 'agent1', '59 * * * * echo "agent1"'
42
+ dc.add 'agent2', {:hour=>'2',:command=>'echo "huh"'}
43
+ dc.commit
44
+ puts dc
45
+
46
+ # 7. Of course you can combine (merge/subtract) all types of Crontabs, ie. Crontab, FileCrontab, DummyCrontab
47
+
48
+ # 8. In case you need to read/write crontab definition from/to yet another source (stream) use setIO method
49
+ require 'stringio'
50
+ output = "#read from DB"
51
+ p Crontab.new.setIO(StringIO.new(output),nil).list
@@ -0,0 +1,14 @@
1
+ MAILTO=user
2
+ SHELL = /bin/bash
3
+
4
+ # * * * * wed,sat /usr/bin/ruby -C /sync test.rb run
5
+ 0 20 * * * /tmp/agent1
6
+ # this is a comment - to be preserved
7
+
8
+ ##__test__
9
+ 2 * * * * /verycomplicated/script 123 >> /tmp/xml_server.log 2>&1
10
+
11
+ ##__test2__
12
+ # id is lost?
13
+ 0 21 * * * /sync/exec_with_env.sh jobstream_generate.rb --lang en --fulladmin run
14
+ 0 21 * * * /sync/exec_with_env.sh sync_master.rb --monitconf /etc/monitrc-200 --downtimeFrom '22:00' --downtimeTo '23:00' run
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: cronedit
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.0
7
- date: 2007-12-09 00:00:00 +01:00
6
+ version: 0.3.0
7
+ date: 2008-02-04 00:00:00 +01:00
8
8
  summary: CronEdit is a Ruby editor library for crontab.
9
9
  require_paths:
10
10
  - lib
@@ -30,6 +30,11 @@ authors:
30
30
  - Viktor Zigo
31
31
  files:
32
32
  - lib/cronedit.rb
33
+ - test/testcron.txt
34
+ - test/examples
35
+ - test/examples/example1.cron
36
+ - test/examples/examples.rb
37
+ - test/cronedit_test.rb
33
38
  test_files:
34
39
  - test/cronedit_test.rb
35
40
  rdoc_options: []