comparison roundup-admin @ 377:93dc08528ad9

roundup-admin now handles all hyperdb exceptions
author Richard Jones <richard@users.sourceforge.net>
date Fri, 09 Nov 2001 10:11:08 +0000
parents c6d6ea15068b
children c7b5b1aa6b4a
comparison
equal deleted inserted replaced
376:c6d6ea15068b 377:93dc08528ad9
14 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 14 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
15 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" 15 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
16 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, 16 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
18 # 18 #
19 # $Id: roundup-admin,v 1.41 2001-11-09 01:25:40 richard Exp $ 19 # $Id: roundup-admin,v 1.42 2001-11-09 10:11:08 richard Exp $
20 20
21 import sys 21 import sys
22 if int(sys.version[0]) < 2: 22 if int(sys.version[0]) < 2:
23 print 'Roundup requires python 2.0 or later.' 23 print 'Roundup requires python 2.0 or later.'
24 sys.exit(1) 24 sys.exit(1)
48 l.append((ki, self.data[ki])) 48 l.append((ki, self.data[ki]))
49 if not l and default is self._marker: 49 if not l and default is self._marker:
50 raise KeyError, key 50 raise KeyError, key
51 return l 51 return l
52 52
53 class UsageError(ValueError):
54 pass
55
53 class AdminTool: 56 class AdminTool:
54 57
55 def __init__(self): 58 def __init__(self):
56 self.commands = CommandDict() 59 self.commands = CommandDict()
57 for k in AdminTool.__dict__.keys(): 60 for k in AdminTool.__dict__.keys():
157 topic = args[0] 160 topic = args[0]
158 161
159 # try help_ methods 162 # try help_ methods
160 if self.help.has_key(topic): 163 if self.help.has_key(topic):
161 self.help[topic]() 164 self.help[topic]()
162 return 165 return 0
163 166
164 # try command docstrings 167 # try command docstrings
165 try: 168 try:
166 l = self.commands.get(topic) 169 l = self.commands.get(topic)
167 except KeyError: 170 except KeyError:
168 print 'Sorry, no help for "%s"'%topic 171 print 'Sorry, no help for "%s"'%topic
169 return 172 return 1
170 173
171 # display the help for each match, removing the docsring indent 174 # display the help for each match, removing the docsring indent
172 for name, help in l: 175 for name, help in l:
173 lines = nl_re.split(help.__doc__) 176 lines = nl_re.split(help.__doc__)
174 print lines[0] 177 print lines[0]
177 for line in lines[1:]: 180 for line in lines[1:]:
178 if indent: 181 if indent:
179 print line[indent:] 182 print line[indent:]
180 else: 183 else:
181 print line 184 print line
185 return 0
182 186
183 def help_initopts(self): 187 def help_initopts(self):
184 import roundup.templates 188 import roundup.templates
185 templates = roundup.templates.listTemplates() 189 templates = roundup.templates.listTemplates()
186 print 'Templates:', ', '.join(templates) 190 print 'Templates:', ', '.join(templates)
240 ''' 244 '''
241 propname = args[0] 245 propname = args[0]
242 designators = string.split(args[1], ',') 246 designators = string.split(args[1], ',')
243 l = [] 247 l = []
244 for designator in designators: 248 for designator in designators:
249 # decode the node designator
245 try: 250 try:
246 classname, nodeid = roundupdb.splitDesignator(designator) 251 classname, nodeid = roundupdb.splitDesignator(designator)
247 except roundupdb.DesignatorError, message: 252 except roundupdb.DesignatorError, message:
248 print 'Error: %s'%message 253 raise UsageError, message
249 return 1 254
250 if self.comma_sep: 255 # get the class
251 l.append(self.db.getclass(classname).get(nodeid, propname)) 256 try:
252 else: 257 cl = self.db.getclass(classname)
253 print self.db.getclass(classname).get(nodeid, propname) 258 except KeyError:
259 raise UsageError, 'invalid class "%s"'%classname
260 try:
261 if self.comma_sep:
262 l.append(cl.get(nodeid, propname))
263 else:
264 print cl.get(nodeid, propname)
265 except IndexError:
266 raise UsageError, 'no such %s node "%s"'%(classname, nodeid)
267 except KeyError:
268 raise UsageError, 'no such %s property "%s"'%(classname,
269 propname)
254 if self.comma_sep: 270 if self.comma_sep:
255 print ','.join(l) 271 print ','.join(l)
256 return 0 272 return 0
257 273
258 274
265 from roundup import hyperdb 281 from roundup import hyperdb
266 282
267 designators = string.split(args[0], ',') 283 designators = string.split(args[0], ',')
268 props = {} 284 props = {}
269 for prop in args[1:]: 285 for prop in args[1:]:
270 key, value = prop.split('=') 286 if prop.find('=') == -1:
287 raise UsageError, 'argument "%s" not propname=value'%prop
288 try:
289 key, value = prop.split('=')
290 except (TypeError, ValueError):
291 raise UsageError, 'argument "%s" not propname=value'%prop
271 props[key] = value 292 props[key] = value
272 for designator in designators: 293 for designator in designators:
294 # decode the node designator
273 try: 295 try:
274 classname, nodeid = roundupdb.splitDesignator(designator) 296 classname, nodeid = roundupdb.splitDesignator(designator)
275 except roundupdb.DesignatorError, message: 297 except roundupdb.DesignatorError, message:
276 print 'Error: %s'%message 298 raise UsageError, message
277 return 1 299
278 cl = self.db.getclass(classname) 300 # get the class
301 try:
302 cl = self.db.getclass(classname)
303 except KeyError:
304 raise UsageError, 'invalid class "%s"'%classname
305
279 properties = cl.getprops() 306 properties = cl.getprops()
280 for key, value in props.items(): 307 for key, value in props.items():
281 type = properties[key] 308 type = properties[key]
282 if isinstance(type, hyperdb.String): 309 if isinstance(type, hyperdb.String):
283 continue 310 continue
284 elif isinstance(type, hyperdb.Password): 311 elif isinstance(type, hyperdb.Password):
285 props[key] = password.Password(value) 312 props[key] = password.Password(value)
286 elif isinstance(type, hyperdb.Date): 313 elif isinstance(type, hyperdb.Date):
287 props[key] = date.Date(value) 314 try:
315 props[key] = date.Date(value)
316 except ValueError, message:
317 raise UsageError, '"%s": %s'%(value, message)
288 elif isinstance(type, hyperdb.Interval): 318 elif isinstance(type, hyperdb.Interval):
289 props[key] = date.Interval(value) 319 try:
320 props[key] = date.Interval(value)
321 except ValueError, message:
322 raise UsageError, '"%s": %s'%(value, message)
290 elif isinstance(type, hyperdb.Link): 323 elif isinstance(type, hyperdb.Link):
291 props[key] = value 324 props[key] = value
292 elif isinstance(type, hyperdb.Multilink): 325 elif isinstance(type, hyperdb.Multilink):
293 props[key] = value.split(',') 326 props[key] = value.split(',')
294 apply(cl.set, (nodeid, ), props) 327
328 # try the set
329 try:
330 apply(cl.set, (nodeid, ), props)
331 except (TypeError, IndexError, ValueError), message:
332 raise UsageError, message
295 return 0 333 return 0
296 334
297 def do_find(self, args): 335 def do_find(self, args):
298 '''Usage: find classname propname=value ... 336 '''Usage: find classname propname=value ...
299 Find the nodes of the given class with a given link property value. 337 Find the nodes of the given class with a given link property value.
300 338
301 Find the nodes of the given class with a given link property value. The 339 Find the nodes of the given class with a given link property value. The
302 value may be either the nodeid of the linked node, or its key value. 340 value may be either the nodeid of the linked node, or its key value.
303 ''' 341 '''
304 classname = args[0] 342 classname = args[0]
305 cl = self.db.getclass(classname) 343 # get the class
306 344 try:
307 # look up the linked-to class and get the nodeid that has the value 345 cl = self.db.getclass(classname)
308 propname, value = args[1].split('=') 346 except KeyError:
347 raise UsageError, 'invalid class "%s"'%classname
348
349 # TODO: handle > 1 argument
350 # handle the propname=value argument
351 if prop.find('=') == -1:
352 raise UsageError, 'argument "%s" not propname=value'%prop
353 try:
354 propname, value = args[1].split('=')
355 except (TypeError, ValueError):
356 raise UsageError, 'argument "%s" not propname=value'%prop
357
358 # if the value isn't a number, look up the linked class to get the
359 # number
309 num_re = re.compile('^\d+$') 360 num_re = re.compile('^\d+$')
310 if not num_re.match(value): 361 if not num_re.match(value):
311 propcl = cl.properties[propname] 362 # get the property
312 if (not isinstance(propcl, hyperdb.Link) and not 363 try:
364 property = cl.properties[propname]
365 except KeyError:
366 raise UsageError, '%s has no property "%s"'%(classname,
367 propname)
368
369 # make sure it's a link
370 if (not isinstance(property, hyperdb.Link) and not
313 isinstance(type, hyperdb.Multilink)): 371 isinstance(type, hyperdb.Multilink)):
314 print 'You may only "find" link properties' 372 raise UsageError, 'You may only "find" link properties'
315 return 1 373
316 propcl = self.db.getclass(propcl.classname) 374 # get the linked-to class and look up the key property
317 value = propcl.lookup(value) 375 link_class = self.db.getclass(property.classname)
318 376 try:
319 # now do the find 377 value = link_class.lookup(value)
320 if self.comma_sep: 378 except TypeError:
321 print ','.join(apply(cl.find, (), {propname: value})) 379 raise UsageError, '%s has no key property"'%link_class.classname
322 else: 380 except KeyError:
323 print apply(cl.find, (), {propname: value}) 381 raise UsageError, '%s has no entry "%s"'%(link_class.classname,
382 propname)
383
384 # now do the find
385 try:
386 if self.comma_sep:
387 print ','.join(apply(cl.find, (), {propname: value}))
388 else:
389 print apply(cl.find, (), {propname: value})
390 except KeyError:
391 raise UsageError, '%s has no property "%s"'%(classname,
392 propname)
393 except (ValueError, TypeError), message:
394 raise UsageError, message
324 return 0 395 return 0
325 396
326 def do_specification(self, args): 397 def do_specification(self, args):
327 '''Usage: specification classname 398 '''Usage: specification classname
328 Show the properties for a classname. 399 Show the properties for a classname.
329 400
330 This lists the properties for a given class. 401 This lists the properties for a given class.
331 ''' 402 '''
332 classname = args[0] 403 classname = args[0]
333 cl = self.db.getclass(classname) 404 # get the class
405 try:
406 cl = self.db.getclass(classname)
407 except KeyError:
408 raise UsageError, 'invalid class "%s"'%classname
409
410 # get the key property
334 keyprop = cl.getkey() 411 keyprop = cl.getkey()
335 for key, value in cl.properties.items(): 412 for key, value in cl.properties.items():
336 if keyprop == key: 413 if keyprop == key:
337 print '%s: %s (key property)'%(key, value) 414 print '%s: %s (key property)'%(key, value)
338 else: 415 else:
347 command. 424 command.
348 ''' 425 '''
349 from roundup import hyperdb 426 from roundup import hyperdb
350 427
351 classname = args[0] 428 classname = args[0]
352 cl = self.db.getclass(classname) 429
430 # get the class
431 try:
432 cl = self.db.getclass(classname)
433 except KeyError:
434 raise UsageError, 'invalid class "%s"'%classname
435
436 # now do a create
353 props = {} 437 props = {}
354 properties = cl.getprops(protected = 0) 438 properties = cl.getprops(protected = 0)
355 if len(args) == 1: 439 if len(args) == 1:
356 # ask for the properties 440 # ask for the properties
357 for key, value in properties.items(): 441 for key, value in properties.items():
370 if value: 454 if value:
371 props[key] = value 455 props[key] = value
372 else: 456 else:
373 # use the args 457 # use the args
374 for prop in args[1:]: 458 for prop in args[1:]:
375 key, value = prop.split('=') 459 if prop.find('=') == -1:
460 raise UsageError, 'argument "%s" not propname=value'%prop
461 try:
462 key, value = prop.split('=')
463 except (TypeError, ValueError):
464 raise UsageError, 'argument "%s" not propname=value'%prop
376 props[key] = value 465 props[key] = value
377 466
378 # convert types 467 # convert types
379 for key in props.keys(): 468 for key in props.keys():
380 type = properties[key] 469 # get the property
470 try:
471 type = properties[key]
472 except KeyError:
473 raise UsageError, '%s has no property "%s"'%(classname, key)
474
381 if isinstance(type, hyperdb.Date): 475 if isinstance(type, hyperdb.Date):
382 props[key] = date.Date(value) 476 try:
477 props[key] = date.Date(value)
478 except ValueError, message:
479 raise UsageError, '"%s": %s'%(value, message)
383 elif isinstance(type, hyperdb.Interval): 480 elif isinstance(type, hyperdb.Interval):
384 props[key] = date.Interval(value) 481 try:
482 props[key] = date.Interval(value)
483 except ValueError, message:
484 raise UsageError, '"%s": %s'%(value, message)
385 elif isinstance(type, hyperdb.Password): 485 elif isinstance(type, hyperdb.Password):
386 props[key] = password.Password(value) 486 props[key] = password.Password(value)
387 elif isinstance(type, hyperdb.Multilink): 487 elif isinstance(type, hyperdb.Multilink):
388 props[key] = value.split(',') 488 props[key] = value.split(',')
389 489
490 # check for the key property
390 if cl.getkey() and not props.has_key(cl.getkey()): 491 if cl.getkey() and not props.has_key(cl.getkey()):
391 print "You must provide the '%s' property."%cl.getkey() 492 raise UsageError, "you must provide the '%s' property."%cl.getkey()
392 else: 493
494 # do the actual create
495 try:
393 print apply(cl.create, (), props) 496 print apply(cl.create, (), props)
394 497 except (TypeError, IndexError, ValueError), message:
498 raise UsageError, message
395 return 0 499 return 0
396 500
397 def do_list(self, args): 501 def do_list(self, args):
398 '''Usage: list classname [property] 502 '''Usage: list classname [property]
399 List the instances of a class. 503 List the instances of a class.
402 specified, the "label" property is used. The label property is tried 506 specified, the "label" property is used. The label property is tried
403 in order: the key, "name", "title" and then the first property, 507 in order: the key, "name", "title" and then the first property,
404 alphabetically. 508 alphabetically.
405 ''' 509 '''
406 classname = args[0] 510 classname = args[0]
407 cl = self.db.getclass(classname) 511
512 # get the class
513 try:
514 cl = self.db.getclass(classname)
515 except KeyError:
516 raise UsageError, 'invalid class "%s"'%classname
517
518 # figure the property
408 if len(args) > 1: 519 if len(args) > 1:
409 key = args[1] 520 key = args[1]
410 else: 521 else:
411 key = cl.labelprop() 522 key = cl.labelprop()
523
412 if self.comma_sep: 524 if self.comma_sep:
413 print ','.join(cl.list()) 525 print ','.join(cl.list())
414 else: 526 else:
415 for nodeid in cl.list(): 527 for nodeid in cl.list():
416 value = cl.get(nodeid, key) 528 try:
529 value = cl.get(nodeid, key)
530 except KeyError:
531 raise UsageError, '%s has no property "%s"'%(classname, key)
417 print "%4s: %s"%(nodeid, value) 532 print "%4s: %s"%(nodeid, value)
418 return 0 533 return 0
419 534
420 def do_table(self, args): 535 def do_table(self, args):
421 '''Usage: table classname [property[,property]*] 536 '''Usage: table classname [property[,property]*]
431 2 bug 546 2 bug
432 3 usability 547 3 usability
433 4 feature 548 4 feature
434 ''' 549 '''
435 classname = args[0] 550 classname = args[0]
436 cl = self.db.getclass(classname) 551
552 # get the class
553 try:
554 cl = self.db.getclass(classname)
555 except KeyError:
556 raise UsageError, 'invalid class "%s"'%classname
557
558 # figure the property names to display
437 if len(args) > 1: 559 if len(args) > 1:
438 prop_names = args[1].split(',') 560 prop_names = args[1].split(',')
439 else: 561 else:
440 prop_names = cl.getprops().keys() 562 prop_names = cl.getprops().keys()
563
564 # now figure column widths
441 props = [] 565 props = []
442 for name in prop_names: 566 for spec in prop_names:
443 if ':' in name: 567 if ':' in spec:
444 name, width = name.split(':') 568 try:
445 props.append((name, int(width))) 569 name, width = spec.split(':')
570 except (ValueError, TypeError):
571 raise UsageError, '"%s" not name:width'%spec
572 props.append((spec, int(width)))
446 else: 573 else:
447 props.append((name, len(name))) 574 props.append((spec, len(spec)))
448 575
576 # now display the heading
449 print ' '.join([string.capitalize(name) for name, width in props]) 577 print ' '.join([string.capitalize(name) for name, width in props])
578
579 # and the table data
450 for nodeid in cl.list(): 580 for nodeid in cl.list():
451 l = [] 581 l = []
452 for name, width in props: 582 for name, width in props:
453 if name != 'id': 583 if name != 'id':
454 value = str(cl.get(nodeid, name)) 584 try:
585 value = str(cl.get(nodeid, name))
586 except KeyError:
587 raise UsageError, '%s has no property "%s"'%(classname,
588 name)
455 else: 589 else:
456 value = str(nodeid) 590 value = str(nodeid)
457 f = '%%-%ds'%width 591 f = '%%-%ds'%width
458 l.append(f%value[:width]) 592 l.append(f%value[:width])
459 print ' '.join(l) 593 print ' '.join(l)
466 Lists the journal entries for the node identified by the designator. 600 Lists the journal entries for the node identified by the designator.
467 ''' 601 '''
468 try: 602 try:
469 classname, nodeid = roundupdb.splitDesignator(args[0]) 603 classname, nodeid = roundupdb.splitDesignator(args[0])
470 except roundupdb.DesignatorError, message: 604 except roundupdb.DesignatorError, message:
471 print 'Error: %s'%message 605 raise UsageError, message
472 return 1 606
473 # TODO: handle the -c option? 607 # TODO: handle the -c option?
474 print self.db.getclass(classname).history(nodeid) 608 try:
609 print self.db.getclass(classname).history(nodeid)
610 except KeyError:
611 raise UsageError, 'no such class "%s"'%classname
612 except IndexError:
613 raise UsageError, 'no such %s node "%s"'%(classname, nodeid)
475 return 0 614 return 0
476 615
477 def do_retire(self, args): 616 def do_retire(self, args):
478 '''Usage: retire designator[,designator]* 617 '''Usage: retire designator[,designator]*
479 Retire the node specified by designator. 618 Retire the node specified by designator.
484 designators = string.split(args[0], ',') 623 designators = string.split(args[0], ',')
485 for designator in designators: 624 for designator in designators:
486 try: 625 try:
487 classname, nodeid = roundupdb.splitDesignator(designator) 626 classname, nodeid = roundupdb.splitDesignator(designator)
488 except roundupdb.DesignatorError, message: 627 except roundupdb.DesignatorError, message:
489 print 'Error: %s'%message 628 raise UsageError, message
490 return 1 629 try:
491 self.db.getclass(classname).retire(nodeid) 630 self.db.getclass(classname).retire(nodeid)
631 except KeyError:
632 raise UsageError, 'no such class "%s"'%classname
633 except IndexError:
634 raise UsageError, 'no such %s node "%s"'%(classname, nodeid)
492 return 0 635 return 0
493 636
494 def do_export(self, args): 637 def do_export(self, args):
495 '''Usage: export class[,class] destination_dir 638 '''Usage: export class[,class] destination_dir
496 Export the database to tab-separated-value files. 639 Export the database to tab-separated-value files.
509 if csv is not None: 652 if csv is not None:
510 p = csv.parser(field_sep=':') 653 p = csv.parser(field_sep=':')
511 654
512 # do all the classes specified 655 # do all the classes specified
513 for classname in classes: 656 for classname in classes:
514 cl = self.db.getclass(classname) 657 try:
658 cl = self.db.getclass(classname)
659 except KeyError:
660 raise UsageError, 'no such class "%s"'%classname
515 f = open(os.path.join(dir, classname+'.csv'), 'w') 661 f = open(os.path.join(dir, classname+'.csv'), 'w')
516 f.write(string.join(cl.properties.keys(), ':') + '\n') 662 f.write(string.join(cl.properties.keys(), ':') + '\n')
517 663
518 # all nodes for this class 664 # all nodes for this class
519 properties = cl.properties.items() 665 properties = cl.properties.items()
554 the existing database - if you want to create a new database using the 700 the existing database - if you want to create a new database using the
555 imported data, then create a new database (or, tediously, retire all 701 imported data, then create a new database (or, tediously, retire all
556 the old data.) 702 the old data.)
557 ''' 703 '''
558 if len(args) < 2: 704 if len(args) < 2:
559 print do_import.__doc__ 705 raise UsageError, 'Not enough arguments supplied'
560 return 1
561 if csv is None: 706 if csv is None:
562 print 'Sorry, you need the csv module to use this function.' 707 raise UsageError, \
563 print 'Get it from: http://www.object-craft.com.au/projects/csv/' 708 'Sorry, you need the csv module to use this function.\n'\
564 return 1 709 'Get it from: http://www.object-craft.com.au/projects/csv/'
565 710
566 from roundup import hyperdb 711 from roundup import hyperdb
567 712
568 # ensure that the properties and the CSV file headings match 713 # ensure that the properties and the CSV file headings match
569 cl = self.db.getclass(args[0]) 714 try:
715 cl = self.db.getclass(classname)
716 except KeyError:
717 raise UsageError, 'no such class "%s"'%classname
570 f = open(args[1]) 718 f = open(args[1])
571 p = csv.parser(field_sep=':') 719 p = csv.parser(field_sep=':')
572 file_props = p.parse(f.readline()) 720 file_props = p.parse(f.readline())
573 props = cl.properties.keys() 721 props = cl.properties.keys()
574 m = file_props[:] 722 m = file_props[:]
575 m.sort() 723 m.sort()
576 props.sort() 724 props.sort()
577 if m != props: 725 if m != props:
578 print 'Import file doesn\'t define the same properties as "%s".'%args[0] 726 raise UsageError, 'Import file doesn\'t define the same '\
579 return 1 727 'properties as "%s".'%args[0]
580 728
581 # loop through the file and create a node for each entry 729 # loop through the file and create a node for each entry
582 n = range(len(props)) 730 n = range(len(props))
583 while 1: 731 while 1:
584 line = f.readline() 732 line = f.readline()
661 if len(args) < 2: 809 if len(args) < 2:
662 print function.__doc__ 810 print function.__doc__
663 return 1 811 return 1
664 812
665 # do the command 813 # do the command
666 try: 814 ret = 0
667 return function(args[1:]) 815 try:
668 finally: 816 ret = function(args[1:])
669 self.db.close() 817 except UsageError, message:
670 818 print 'Error: %s'%message
671 return 1 819 print function.__doc__
820 ret = 1
821 except:
822 import traceback
823 traceback.print_exc()
824 ret = 1
825 return ret
672 826
673 def interactive(self, ws_re=re.compile(r'\s+')): 827 def interactive(self, ws_re=re.compile(r'\s+')):
674 '''Run in an interactive mode 828 '''Run in an interactive mode
675 ''' 829 '''
676 print 'Roundup {version} ready for input.' 830 print 'Roundup {version} ready for input.'
711 self.instance_home = arg 865 self.instance_home = arg
712 if opt == '-c': 866 if opt == '-c':
713 self.comma_sep = 1 867 self.comma_sep = 1
714 868
715 # if no command - go interactive 869 # if no command - go interactive
870 ret = 0
716 if not args: 871 if not args:
717 return self.interactive() 872 self.interactive()
718 873 else:
719 self.run_command(args) 874 ret = self.run_command(args)
875 self.db.close()
876 return ret
720 877
721 878
722 if __name__ == '__main__': 879 if __name__ == '__main__':
723 tool = AdminTool() 880 tool = AdminTool()
724 sys.exit(tool.main()) 881 sys.exit(tool.main())
725 882
726 # 883 #
727 # $Log: not supported by cvs2svn $ 884 # $Log: not supported by cvs2svn $
885 # Revision 1.41 2001/11/09 01:25:40 richard
886 # Should parse with python 1.5.2 now.
887 #
728 # Revision 1.40 2001/11/08 04:42:00 richard 888 # Revision 1.40 2001/11/08 04:42:00 richard
729 # Expanded the already-abbreviated "initialise" and "specification" commands, 889 # Expanded the already-abbreviated "initialise" and "specification" commands,
730 # and added a comment to the command help about the abbreviation. 890 # and added a comment to the command help about the abbreviation.
731 # 891 #
732 # Revision 1.39 2001/11/08 04:29:59 richard 892 # Revision 1.39 2001/11/08 04:29:59 richard

Roundup Issue Tracker: http://roundup-tracker.org/