cronedit 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/cronedit.rb +334 -0
  2. data/test/cronedit_test.rb +99 -0
  3. metadata +46 -0
@@ -0,0 +1,334 @@
1
+ # :title:CronEdit - Ruby editior library for cron and crontab - RDoc documentation
2
+ # =CronEdit - Ruby editor library for crontab.
3
+ #
4
+ # Allows to manipulate crontab from comfortably from ruby code.
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.
8
+ #
9
+ # ==Usage
10
+ #
11
+ # Class methods offer quick crontab operations. Three examples:
12
+ # CronEdit::Crontab.Add 'agent1', '5,35 0-23/2 * * * echo agent1'
13
+ # CronEdit::Crontab.Add 'agent2', {:minute=>5, :command=>'echo 42'}
14
+ # CronEdit::Crontab.Remove 'someId'
15
+ #
16
+ # or define a batch update and list the current content:
17
+ #
18
+ # cm = CronEdit:Crontab.new 'user'
19
+ # cm.add 'agent1', '5,35 0-23/2 * * * echo agent1'
20
+ # ...
21
+ # cm.add 'agent2', {:minute=>5, :command=>'echo 42'}
22
+ # cm.commit
23
+ # p cm.list
24
+ #
25
+ # see Crontab for all available methods
26
+ # ==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)
29
+ # ==History
30
+ # version: 0.2.0
31
+ #
32
+ # ==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
35
+ module CronEdit
36
+ self::VERSION = '0.2.0' unless defined? self::VERSION
37
+
38
+ class Crontab
39
+ # Use crontab for user aUser
40
+ def initialize aUser = nil
41
+ @user = aUser
42
+ rollback()
43
+ end
44
+
45
+ class <<self
46
+ # Add a new crontab entry definition. See instance add method
47
+ def Add anId, aDef
48
+ cm = Crontab.new
49
+ entry = cm.add anId, aDef
50
+ cm.commit
51
+ entry
52
+ end
53
+
54
+ # Remove a crontab entry definition identified by anId from current crontab.
55
+ def Remove anId
56
+ cm = Crontab.new
57
+ cm.remove anId
58
+ cm.commit
59
+ end
60
+
61
+ # List current crontab.
62
+ def List
63
+ Crontab.new.list
64
+ end
65
+ end
66
+
67
+ # Add a new crontab entry definition. Becomes effective only after commit().
68
+ # * aDef is can be a standart text definition or a Hash definition (see CronEntry::DEFAULTS)
69
+ # * anId is an identification of the entry (for later modification or deletion)
70
+ # returns newly added CronEntry
71
+ def add anId, aDef
72
+ @adds[anId.to_s] = CronEntry.new( aDef )
73
+ end
74
+
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'
79
+ end
80
+
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
85
+ end
86
+
87
+ # Merges the existing crontab with all modifications and installs the new crontab.
88
+ # returns the merged parsed crontab hash
89
+ def commit
90
+ # merge crontab
91
+ current = list()
92
+ current.delete_if {|id, entry| @removals.include? id}
93
+ merged = current.merge @adds
94
+
95
+ # 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
+ }
104
+ # No idea why but without this any wait crontab reads and writes appears not synchronizes
105
+ sleep 0.01
106
+ #clean changes :)
107
+ rollback()
108
+ merged
109
+ end
110
+
111
+ # Discards all modifications (since last commit, or creation)
112
+ def rollback
113
+ @adds = {}
114
+ @removals = {}
115
+ end
116
+
117
+ # Prints out the items to be added and removed
118
+ def review
119
+ puts "To be added: #{@adds.inspect}"
120
+ puts "To be removed: #{@removals.keys.inspect}"
121
+ end
122
+
123
+ # Read the current crontab and parse it
124
+ # returns a Hash (entry id or index)=>CronEntry
125
+ def list
126
+ cmd = @user ? "crontab -u #{@user} -l" : "crontab -l"
127
+ IO.popen(cmd) {|io|
128
+ return parseCrontab(io)
129
+ }
130
+ end
131
+
132
+ # Lists raw content from crontab
133
+ # returns array of text lines
134
+ 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
+ }
140
+ end
141
+
142
+ def parseCrontab anIO
143
+ entries = {}
144
+ idx = 0
145
+ id = nil
146
+ anIO.each_line { |l|
147
+ l.strip!
148
+ 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
154
+ entries[key.to_s]=l
155
+ id = nil
156
+ end
157
+ }
158
+ entries
159
+ end
160
+ end
161
+
162
+
163
+ class CronEntry
164
+ DEFAULTS = {
165
+ :minute => '*',
166
+ :hour => '*',
167
+ :day => '*',
168
+ :month => '*',
169
+ :weekday => '*',
170
+ :command => ''
171
+ }
172
+
173
+ class FormatError < StandardError; end
174
+
175
+ # Hash def, or raw String def
176
+ def initialize aDef = {}
177
+ if aDef.kind_of? Hash
178
+ wrong = aDef.collect { |k,v| DEFAULTS.include?(k) ? nil : k}.compact
179
+ raise "Wrong definition, invalid constructs #{wrong}" unless wrong.empty?
180
+ @defHash = DEFAULTS.merge aDef
181
+ # TODO: validate values
182
+ @def = to_raw @defHash ;
183
+ else
184
+ @defHash = parseTextDef aDef
185
+ @def = aDef;
186
+ end
187
+ end
188
+
189
+ def to_s
190
+ @def.freeze
191
+ end
192
+
193
+ def to_hash
194
+ @defHash.freeze
195
+ end
196
+
197
+ def []aField
198
+ @defHash[aField]
199
+ end
200
+
201
+ def to_raw aHash = nil;
202
+ aHash ||= @defHash
203
+ "#{aHash[:minute]}\t#{aHash[:hour]}\t#{aHash[:day]}\t#{aHash[:month]}\t" +
204
+ "#{aHash[:weekday]}\t#{aHash[:command]}"
205
+ end
206
+
207
+ private
208
+
209
+ # Parses a raw text definition of crontab entry
210
+ # returns hash definition
211
+ # Original author of parsing: gotoken@notwork.org
212
+ def parseTextDef aLine
213
+ hashDef = parse_timedate aLine
214
+ hashDef[:command] = aLine.scan(/(?:\S+\s+){5}(.*)/).shift[-1]
215
+ ##TODO: raise( FormatError.new "Command cannot be empty") if aDef[:command].empty?
216
+ hashDef
217
+ end
218
+
219
+ # Original author of parsing: gotoken@notwork.org
220
+ def parse_timedate str, aDefHash = {}
221
+ minute, hour, day_of_month, month, day_of_week =
222
+ str.scan(/^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/).shift
223
+ day_of_week = day_of_week.downcase.gsub(/#{WDAY.join("|")}/){
224
+ WDAY.index($&)
225
+ }
226
+ aDefHash[:minute] = parse_field(minute, 0, 59)
227
+ aDefHash[:hour] = parse_field(hour, 0, 23)
228
+ aDefHash[:day] = parse_field(day_of_month, 1, 31)
229
+ aDefHash[:month] = parse_field(month, 1, 12)
230
+ aDefHash[:weekday] = parse_field(day_of_week, 0, 6)
231
+ aDefHash
232
+ end
233
+
234
+ # Original author of parsing: gotoken@notwork.org
235
+ def parse_field str, first, last
236
+ list = str.split(",")
237
+ list.map!{|r|
238
+ r, every = r.split("/")
239
+ every = every ? every.to_i : 1
240
+ f,l = r.split("-")
241
+ range = if f == "*"
242
+ first..last
243
+ elsif l.nil?
244
+ f.to_i .. f.to_i
245
+ elsif f.to_i < first
246
+ raise FormatError.new( "out of range (#{f} for #{first})")
247
+ elsif last < l.to_i
248
+ raise FormatError.new( "out of range (#{l} for #{last})")
249
+ else
250
+ f.to_i .. l.to_i
251
+ end
252
+ range.to_a.find_all{|i| (i - first) % every == 0}
253
+ }
254
+ list.flatten!
255
+ list.join ','
256
+ end
257
+
258
+ WDAY = %w(sun mon tue wed thu fri sut)
259
+ end
260
+
261
+ end
262
+
263
+ if __FILE__ == $0
264
+ 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
+ end
@@ -0,0 +1,99 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+
3
+ require 'test/unit'
4
+ require 'cronedit.rb'
5
+ include CronEdit
6
+
7
+ class CronEdit_test < Test::Unit::TestCase
8
+ # def setup
9
+ # end
10
+ #
11
+ # def teardown
12
+ # end
13
+
14
+ def test_creation
15
+ e = CronEntry.new( "5,35 0-23/2 * * * echo 123" )
16
+ assert_equal( '5,35 0-23/2 * * * echo 123', e.to_s )
17
+
18
+ e = CronEntry.new
19
+ assert_equal( "*\t*\t*\t*\t*\t", e.to_s, 'default' )
20
+
21
+ e = CronEntry.new( {:minute=>5, :command=>'echo 42'} )
22
+ assert_equal( "5\t*\t*\t*\t*\techo 42", e.to_s )
23
+
24
+ end
25
+
26
+ def test_parsing
27
+ e = CronEntry.new( "5,35 0-23/2 * * * echo 123" )
28
+ assert_equal( "5,35", e[:minute])
29
+ assert_equal( "0,2,4,6,8,10,12,14,16,18,20,22", e[:hour])
30
+ 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
+ end
32
+ def test_wrongformat
33
+ assert_raise(CronEntry::FormatError){
34
+ CronEntry.new( "1-85 2 * * * echo 123" )
35
+ }
36
+ end
37
+
38
+ def test_wrongconfog
39
+ assert_raise(RuntimeError){
40
+ CronEntry.new( {:minuteZ=>5, :command=>'echo 42'} )
41
+ }
42
+ end
43
+
44
+ def test_zip
45
+ crontabTest=%Q{
46
+ 5,35 0-23/2 * * * echo 123
47
+ #agent1
48
+ 3 * * * * echo agent1
49
+
50
+
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')
59
+ end
60
+
61
+ def test_emptycrontab
62
+ assert_equal( [], Crontab.new.listRaw )
63
+ assert_equal( {}, Crontab.new.list )
64
+ end
65
+
66
+ def test_rollback
67
+ #rollback test
68
+ assert_equal( {}, Crontab.new.list, 'precondition' )
69
+ cm = Crontab.new
70
+ cm. add 'agent1', '5,35 0-23/2 * * * "echo 123" '
71
+ cm.remove "agent2"
72
+ #cm.review
73
+ cm.rollback
74
+ assert_equal( {}, Crontab.new.list )
75
+ end
76
+
77
+ def test_commit
78
+ assert_equal( {}, Crontab.new.list, 'precondition' )
79
+ cm = Crontab.new
80
+ cm. add "agent1", "5,35 0-23/2 * * * echo agent1"
81
+ cm. add "agent2", "0 2 * * * echo agent2"
82
+ cm.commit
83
+ current=cm.list
84
+ expected = {"agent1"=>"5,35 0-23/2 * * * echo agent1", "agent2"=>"0 2 * * * echo agent2"}
85
+ assert_equal( expected, current, 'first commit' )
86
+
87
+ cm = Crontab.new
88
+ cm. add "agent1", '59 * * * * echo "modified agent1"'
89
+ cm.remove "agent2"
90
+ cm.commit
91
+ current = cm.list
92
+ expected = {"agent1"=>"59 * * * * echo \"modified agent1\""}
93
+ assert_equal( expected, current, 'second commit' )
94
+
95
+ Crontab.Remove "agent1"
96
+ assert_equal( {}, Crontab.List, 'precondition' )
97
+ end
98
+
99
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: cronedit
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.2.0
7
+ date: 2007-12-09 00:00:00 +01:00
8
+ summary: CronEdit is a Ruby editor library for crontab.
9
+ require_paths:
10
+ - lib
11
+ email: viz@alephzarro.com
12
+ homepage: http://cronedit.rubyforge.org/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: lib
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Viktor Zigo
31
+ files:
32
+ - lib/cronedit.rb
33
+ test_files:
34
+ - test/cronedit_test.rb
35
+ rdoc_options: []
36
+
37
+ extra_rdoc_files: []
38
+
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ requirements: []
44
+
45
+ dependencies: []
46
+