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.
- 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: []
|