1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """
24 Example Use:
25
26 from crontab import CronTab
27
28 tab = CronTab()
29 cron = tab.new(command='/usr/bin/echo')
30
31 cron.minute().during(5,50).every(5)
32 cron.hour().every(4)
33
34 cron2 = tab.new(command='/foo/bar',comment='SomeID')
35 cron2.every_reboot()
36
37 list = tab.find_command('bar')
38 cron3 = list[0]
39 cron3.clear()
40 cron3.minute().every(1)
41
42 print unicode(tab.render())
43
44 for cron4 in tab.find_command('echo'):
45 print cron4
46
47 for cron5 in tab:
48 print cron5
49
50 tab.remove_all('echo')
51
52 t.write()
53 """
54
55 import os, re, sys
56 import tempfile
57
58 __version__ = '0.9.3'
59
60 CRONCMD = "/usr/bin/crontab"
61 ITEMREX = re.compile('^\s*([^@#\s]+)\s+([^@#\s]+)\s+([^@#\s]+)' +
62 '\s+([^@#\s]+)\s+([^@#\s]+)\s+([^#\n]*)(\s+#\s*([^\n]*)|$)')
63 SPECREX = re.compile('@(\w+)\s([^#\n]*)(\s+#\s*([^\n]*)|$)')
64 DEVNULL = ">/dev/null 2>&1"
65
66 MONTH_ENUM = [
67 'jan', 'feb', 'mar', 'apr', 'may',
68 'jun', 'jul', 'aug', 'sep', 'oct',
69 'nov', 'dec',
70 ]
71 WEEK_ENUM = [
72 'sun', 'mon', 'tue', 'wed', 'thu',
73 'fri', 'sat', 'sun',
74 ]
75
76 SPECIALS = {
77 "reboot" : '@reboot',
78 "hourly" : '0 * * * *',
79 "daily" : '0 0 * * *',
80 "weekly" : '0 0 * * 0',
81 "monthly" : '0 0 1 * *',
82 "yearly" : '0 0 1 1 *',
83 "annually": '0 0 1 1 *',
84 "midnight": '0 0 * * *'
85 }
86
87 S_INFO = [
88 { 'name' : 'Minutes', 'max_v' : 59, 'min_v' : 0 },
89 { 'name' : 'Hours', 'max_v' : 23, 'min_v' : 0 },
90 { 'name' : 'Day of Month', 'max_v' : 31, 'min_v' : 1 },
91 { 'name' : 'Month', 'max_v' : 12, 'min_v' : 1, 'enum' : MONTH_ENUM },
92 { 'name' : 'Day of Week', 'max_v' : 7, 'min_v' : 0, 'enum' : WEEK_ENUM },
93 ]
94
96 """
97 Crontab object which can access any time based cron using the standard.
98
99 user = Set the user of the crontab (defaults to $USER)
100 fake_tab = Don't set to crontab at all, set to testable fake tab variable.
101 """
102 - def __init__(self, user=None, fake_tab=None):
103 self.user = user
104 self.root = ( os.getuid() == 0 )
105 self.lines = None
106 self.crons = None
107 self.fake = fake_tab
108 self.read()
109
111 """
112 Read in the crontab from the system into the object, called
113 automatically when listing or using the object. use for refresh.
114 """
115 self.crons = []
116 self.lines = []
117 if self.fake:
118 lines = self.fake.split('\n')
119 else:
120 lines = os.popen(self._read_execute()).readlines()
121 for line in lines:
122 cron = CronItem(line)
123 if cron.is_valid():
124 self.crons.append(cron)
125 self.lines.append(cron)
126 else:
127 self.lines.append(line.replace('\n',''))
128
130 """Write the crontab to the system. Saves all information."""
131
132 if self.fake != None:
133 self.fake = self.render()
134 return
135
136 filed, path = tempfile.mkstemp()
137 fileh = os.fdopen(filed, 'w')
138 fileh.write(self.render())
139 fileh.close()
140
141 os.system(self._write_execute(path))
142 os.unlink(path)
143
145 """Render this crontab as it would be in the crontab."""
146 crons = []
147 for cron in self.lines:
148 if type(cron) == CronItem and not cron.is_valid():
149 crons.append("# " + unicode(cron))
150 sys.stderr.write(
151 "Ignoring invalid crontab line `%s`\n" % str(cron))
152 continue
153 crons.append(unicode(cron))
154 result = '\n'.join(crons)
155
156
157
158 if len(result) == 0 or result[-1] not in [ '\n', '\r' ]:
159 result += '\n'
160 return result
161
162 - def new(self, command='', comment=''):
163 """
164 Create a new cron with a command and comment.
165
166 Returns the new CronItem object.
167 """
168 item = CronItem(command=command, meta=comment)
169 self.crons.append(item)
170 self.lines.append(item)
171 return item
172
174 """Return a list of crons using a command."""
175 result = []
176 for cron in self.crons:
177 if cron.command.match(command):
178 result.append(cron)
179 return result
180
182 """Removes all crons using the stated command."""
183 l_value = self.find_command(command)
184 for c_value in l_value:
185 self.remove(c_value)
186
188 """Remove a selected cron from the crontab."""
189 self.crons.remove(item)
190 self.lines.remove(item)
191
193 """Returns the command line for reading a crontab"""
194 return "%s -l%s" % (CRONCMD, self._user_execute())
195
197 """Return the command line for writing a crontab"""
198 return "%s %s%s" % (CRONCMD, path, self._user_execute())
199
201 """User command switches to append to the read and write commands."""
202 if self.user:
203 return ' -u %s' % str(self.user)
204 return ''
205
208
211
212
214 """
215 An item which objectifies a single line of a crontab and
216 May be considered to be a cron job object.
217 """
218 - def __init__(self, line=None, command='', meta=''):
227
249
251 """Set the values of this slice set"""
252 self.slices = []
253 for i_value in range(0, 5):
254 if not o_value:
255 o_value = [None, None, None, None, None]
256 self.slices.append(
257 CronSlice(value=o_value[i_value], **S_INFO[i_value]))
258
260 """Return true if this slice set is valid"""
261 return self.valid
262
264 """Render this set slice to a string"""
265 time = ''
266 if not self.special:
267 slices = []
268 for i in range(0, 5):
269 slices.append(unicode(self.slices[i]))
270 time = ' '.join(slices)
271 if self.special or time in SPECIALS.values():
272 if self.special:
273 time = self.special
274 else:
275 time = "@%s" % SPECIALS.keys()[SPECIALS.values().index(time)]
276
277 result = "%s %s" % (time, unicode(self.command))
278 if self.meta():
279 result += " # " + self.meta()
280 return result
281
282
288
290 """Set to every reboot instead of a time pattern"""
291 self.special = '@reboot'
292
294 """Clear the special and set values"""
295 self.special = None
296 for slice_v in self.slices:
297 slice_v.clear()
298
300 """Return the minute slice"""
301 return self.slices[0]
302
304 """Return the hour slice"""
305 return self.slices[1]
306
308 """Return the day-of-the month slice"""
309 return self.slices[2]
310
312 """Return the month slice"""
313 return self.slices[3]
314
316 """Return the day of the week slice"""
317 return self.slices[4]
318
321
324
325
327 """Cron slice object which shows a time pattern"""
328 - def __init__(self, name, min_v, max_v, enum=None, value=None):
329 self.name = name
330 self.min = min_v
331 self.max = max_v
332 self.enum = enum
333 self.parts = []
334 self.value(value)
335
336 - def value(self, value=None):
337 """Return the value of the entire slice."""
338 if value:
339 self.parts = []
340 for part in value.split(','):
341 if part.find("/") > 0 or part.find("-") > 0 or part == '*':
342 self.parts.append( self.get_range( part ) )
343 else:
344 if self.enum and part.lower() in self.enum:
345 part = self.enum.index(part.lower())
346 try:
347 self.parts.append( int(part) )
348 except:
349 raise ValueError(
350 'Unknown cron time part for %s: %s' % (
351 self.name, part))
352 return self.render()
353
355 """Return the slice rendered as a crontab"""
356 result = []
357 for part in self.parts:
358 result.append(unicode(part))
359 if not result:
360 return '*'
361 return ','.join(result)
362
365
368
369 - def every(self, n_value):
370 """Set the every X units value"""
371 self.parts = [ self.get_range( '*/%d' % int(n_value) ) ]
372
373 - def on(self, *n_value):
374 """Set the on the time value."""
375 self.parts += n_value
376
377 - def during(self, value_from, value_to):
378 """Set the During value, which sets a range"""
379 range_value = self.get_range(
380 "%s-%s" % (str(value_from), str(value_to)))
381 self.parts.append( range_value )
382 return range_value
383
385 """clear the slice ready for new vaues"""
386 self.parts = []
387
389 """Return a cron range for this slice"""
390 return CronRange( self, range_value )
391
392
394 """A range between one value and another for a time range."""
395 - def __init__(self, slice_value, range_value=None):
396 self.value_from = None
397 self.value_to = None
398 self.slice = slice_value
399 self.seq = 1
400 if not range_value:
401 range_value = '*'
402 self.parse(range_value)
403
405 """Parse a ranged value in a cronjob"""
406 if value.find('/') > 0:
407 value, self.seq = value.split('/')
408 if value.find('-') > 0:
409 from_val, to_val = value.split('-')
410 self.value_from = self.clean_value(from_val)
411 self.value_to = self.clean_value(to_val)
412 elif value == '*':
413 self.value_from = self.slice.min
414 self.value_to = self.slice.max
415 else:
416 raise ValueError, 'Unknown cron range value %s' % value
417
419 """Render the ranged value for a cronjob"""
420 value = '*'
421 if self.value_from > self.slice.min or self.value_to < self.slice.max:
422 value = "%d-%d" % (int(self.value_from), int(self.value_to))
423 if int(self.seq) != 1:
424 value += "/%d" % (int(self.seq))
425 return value
426
428 """Return a cleaned value of the ranged value"""
429 if self.slice.enum and str(value).lower() in self.slice.enum:
430 value = self.slice.enum.index(str(value).lower())
431 try:
432 value = int(value)
433 if value >= self.slice.min and value <= self.slice.max:
434 return value
435 except ValueError:
436 raise ValueError('Invalid range value %s' % str(value))
437
439 """Set the sequence value for this range."""
440 self.seq = int(value)
441
444
447
448
450 """Reprisent a cron command as an object."""
453
454 - def match(self, command):
455 """Match the command given"""
456 if command in self._command:
457 return True
458 return False
459
461 """Return the command line"""
462 return self._command
463
465 """Return a string as a value"""
466 return self.__unicode__()
467
469 """Return unicode command line value"""
470 return self.command()
471