cronedit 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cronedit.rb +304 -128
- data/test/cronedit_test.rb +217 -21
- data/test/examples/example1.cron +4 -0
- data/test/examples/examples.rb +51 -0
- data/test/testcron.txt +14 -0
- metadata +7 -2
data/lib/cronedit.rb
CHANGED
@@ -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
|
7
|
-
#
|
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
|
-
#
|
22
|
+
# Define a batch update and list the current content:
|
17
23
|
#
|
18
|
-
# cm = CronEdit
|
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://
|
28
|
-
# (parts of the
|
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.
|
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
|
34
|
-
#
|
55
|
+
# * add Utils: getNext execution
|
56
|
+
# * platform specific options (headers; vixiecron vs. dilloncron)
|
35
57
|
module CronEdit
|
36
|
-
self::VERSION = '0.
|
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.
|
70
|
+
# Add a new crontab entry definition. (see instance method add).
|
47
71
|
def Add anId, aDef
|
48
|
-
cm =
|
72
|
+
cm = self.new
|
49
73
|
entry = cm.add anId, aDef
|
50
74
|
cm.commit
|
51
75
|
entry
|
52
76
|
end
|
53
77
|
|
54
|
-
#
|
55
|
-
def Remove
|
56
|
-
cm =
|
57
|
-
cm.remove
|
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
|
-
|
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
|
76
|
-
def
|
77
|
-
|
78
|
-
|
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
|
-
#
|
82
|
-
def
|
83
|
-
|
84
|
-
|
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
|
-
|
92
|
-
|
93
|
-
merged = current.merge @adds
|
94
|
-
|
146
|
+
currentEntries, lines = listFull()
|
147
|
+
mergedEntries = nil
|
95
148
|
# install it
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
127
|
-
|
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
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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
|
data/test/cronedit_test.rb
CHANGED
@@ -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
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
55
|
+
|
56
|
+
def test_wrongformat
|
33
57
|
assert_raise(CronEntry::FormatError){
|
34
58
|
CronEntry.new( "1-85 2 * * * echo 123" )
|
35
59
|
}
|
36
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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,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
|
data/test/testcron.txt
ADDED
@@ -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.
|
7
|
-
date:
|
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: []
|