Code

Oops, committed the admin script with the wierd #! line.
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 18 Oct 2001 02:16:42 +0000 (02:16 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 18 Oct 2001 02:16:42 +0000 (02:16 +0000)
Also, made the thing into a class to reduce parameter passing.
Nuked the leading whitespace from the help __doc__ displays too.

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@311 57a73879-2fb5-44c3-a270-3262357dd7e2

roundup-admin

index f7a0ff270e1f988cff590b2e4960d32c54788618..9014bc7cc1b4eba48e7f5579b69b96247bc6f431 100755 (executable)
@@ -1,4 +1,4 @@
-#! /Users/builder/bin/python
+#! /usr/bin/env python
 #
 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
 # This module is free software, and you may redistribute it and/or modify
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup-admin,v 1.33 2001-10-17 23:13:19 richard Exp $
+# $Id: roundup-admin,v 1.34 2001-10-18 02:16:42 richard Exp $
 
 import sys
 if int(sys.version[0]) < 2:
@@ -31,9 +31,21 @@ except ImportError:
 from roundup import date, hyperdb, roundupdb, init, password
 import roundup.instance
 
-def usage(message=''):
-    if message: message = 'Problem: '+message+'\n'
-    print '''%sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments>
+class AdminTool:
+
+    def __init__(self):
+        self.commands = {}
+        for k, v in AdminTool.__dict__.items():
+            if k[:3] == 'do_':
+                self.commands[k[3:]] = v
+        self.help = {}
+        for k, v in AdminTool.__dict__.items():
+            if k[:5] == 'help_':
+                self.help[k[5:]] = v
+
+    def usage(message=''):
+        if message: message = 'Problem: '+message+'\n'
+        print '''%sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments>
 
 Help:
  roundup-admin -h
@@ -44,19 +56,19 @@ Options:
  -i instance home  -- specify the issue tracker "home directory" to administer
  -u                -- the user[:password] to use for commands
  -c                -- when outputting lists of data, just comma-separate them'''%message
-    help_commands()
-
-def help_commands():
-    print 'Commands:',
-    commands = ['']
-    for command in figureCommands().values():
-        h = command.__doc__.split('\n')[0]
-        commands.append(h[7:])
-    commands.sort()
-    print '\n '.join(commands)
-
-def help_all():
-    print '''
+        self.help_commands()
+
+    def help_commands(self):
+        print 'Commands:',
+        commands = ['']
+        for command in self.commands.values():
+            h = command.__doc__.split('\n')[0]
+            commands.append(h[7:])
+        commands.sort()
+        print '\n '.join(commands)
+
+    def help_all(self):
+        print '''
 All commands (except help) require an instance specifier. This is just the path
 to the roundup instance you're working with. A roundup instance is where 
 roundup keeps the database and configuration file that defines an issue
@@ -105,460 +117,456 @@ Date format examples:
 
 Command help:
 '''
-    for name, command in figureCommands().items():
-        print '%s:'%name
-        print '   ',command.__doc__
-
-def do_help(args):
-    '''Usage: help topic
-    Give help about topic.
-
-    commands  -- list commands
-    <command> -- help specific to a command
-    initopts  -- init command options
-    all       -- all available help
-    '''
-    help = figureHelp().get(args[0], None)
-    if help:
-        help()
-        return
-    help = figureCommands().get(args[0], None)
-    if help:
-        print help.__doc__
-    else:
-        print 'Sorry, no help for "%s"'%args[0]
-
-def help_initopts():
-    import roundup.templates
-    templates = roundup.templates.listTemplates()
-    print 'Templates:', ', '.join(templates)
-    import roundup.backends
-    backends = roundup.backends.__all__
-    print 'Back ends:', ', '.join(backends)
-
-
-def do_init(instance_home, args, comma_sep=0):
-    '''Usage: init [template [backend [admin password]]]
-    Initialise a new Roundup instance.
-
-    The command will prompt for the instance home directory (if not supplied
-    through INSTANCE_HOME or the -i option. The template, backend and admin
-    password may be specified on the command-line as arguments, in that order.
-
-    See also initopts help.
-    '''
-    # select template
-    import roundup.templates
-    templates = roundup.templates.listTemplates()
-    template = len(args) > 1 and args[1] or ''
-    if template not in templates:
+        for name, command in self.commands.items():
+            print '%s:'%name
+            print '   ',command.__doc__
+
+    def do_help(self, args, nl_re=re.compile('[\r\n]'),
+            indent_re=re.compile(r'^(\s+)\S+')):
+        '''Usage: help topic
+        Give help about topic.
+
+        commands  -- list commands
+        <command> -- help specific to a command
+        initopts  -- init command options
+        all       -- all available help
+        '''
+        help = self.help.get(args[0], None)
+        if help:
+            help(self)
+            return
+        help = self.commands.get(args[0], None)
+        if help:
+            # display the help, removing the docsring indent
+            lines = nl_re.split(help.__doc__)
+            print lines[0]
+            indent = indent_re.match(lines[1])
+            if indent: indent = len(indent.group(1))
+            for line in lines[1:]:
+                if indent:
+                    print line[indent:]
+                else:
+                    print line
+        else:
+            print 'Sorry, no help for "%s"'%args[0]
+
+    def help_initopts(self):
+        import roundup.templates
+        templates = roundup.templates.listTemplates()
         print 'Templates:', ', '.join(templates)
-    while template not in templates:
-        template = raw_input('Select template [extended]: ').strip()
-        if not template:
-            template = 'extended'
-
-    import roundup.backends
-    backends = roundup.backends.__all__
-    backend = len(args) > 2 and args[2] or ''
-    if backend not in backends:
+        import roundup.backends
+        backends = roundup.backends.__all__
         print 'Back ends:', ', '.join(backends)
-    while backend not in backends:
-        backend = raw_input('Select backend [anydbm]: ').strip()
-        if not backend:
-            backend = 'anydbm'
-    if len(args) > 3:
-        adminpw = confirm = args[3]
-    else:
-        adminpw = ''
-        confirm = 'x'
-    while adminpw != confirm:
-        adminpw = getpass.getpass('Admin Password: ')
-        confirm = getpass.getpass('       Confirm: ')
-    init.init(instance_home, template, backend, adminpw)
-    return 0
-
-
-def do_get(db, args, comma_sep=0):
-    '''Usage: get property designator[,designator]*
-    Get the given property of one or more designator(s).
-
-    Retrieves the property value of the nodes specified by the designators.
-    '''
-    propname = args[0]
-    designators = string.split(args[1], ',')
-    l = []
-    for designator in designators:
-        classname, nodeid = roundupdb.splitDesignator(designator)
-        if comma_sep:
-            l.append(db.getclass(classname).get(nodeid, propname))
+
+
+    def do_init(instance_home, args):
+        '''Usage: init [template [backend [admin password]]]
+        Initialise a new Roundup instance.
+
+        The command will prompt for the instance home directory (if not supplied
+        through INSTANCE_HOME or the -i option. The template, backend and admin
+        password may be specified on the command-line as arguments, in that
+        order.
+
+        See also initopts help.
+        '''
+        # select template
+        import roundup.templates
+        templates = roundup.templates.listTemplates()
+        template = len(args) > 1 and args[1] or ''
+        if template not in templates:
+            print 'Templates:', ', '.join(templates)
+        while template not in templates:
+            template = raw_input('Select template [extended]: ').strip()
+            if not template:
+                template = 'extended'
+
+        import roundup.backends
+        backends = roundup.backends.__all__
+        backend = len(args) > 2 and args[2] or ''
+        if backend not in backends:
+            print 'Back ends:', ', '.join(backends)
+        while backend not in backends:
+            backend = raw_input('Select backend [anydbm]: ').strip()
+            if not backend:
+                backend = 'anydbm'
+        if len(args) > 3:
+            adminpw = confirm = args[3]
+        else:
+            adminpw = ''
+            confirm = 'x'
+        while adminpw != confirm:
+            adminpw = getpass.getpass('Admin Password: ')
+            confirm = getpass.getpass('       Confirm: ')
+        init.init(instance_home, template, backend, adminpw)
+        return 0
+
+
+    def do_get(self, args):
+        '''Usage: get property designator[,designator]*
+        Get the given property of one or more designator(s).
+
+        Retrieves the property value of the nodes specified by the designators.
+        '''
+        propname = args[0]
+        designators = string.split(args[1], ',')
+        l = []
+        for designator in designators:
+            classname, nodeid = roundupdb.splitDesignator(designator)
+            if self.comma_sep:
+                l.append(self.db.getclass(classname).get(nodeid, propname))
+            else:
+                print self.db.getclass(classname).get(nodeid, propname)
+        if self.comma_sep:
+            print ','.join(l)
+        return 0
+
+
+    def do_set(self, args):
+        '''Usage: set designator[,designator]* propname=value ...
+        Set the given property of one or more designator(s).
+
+        Sets the property to the value for all designators given.
+        '''
+        from roundup import hyperdb
+
+        designators = string.split(args[0], ',')
+        props = {}
+        for prop in args[1:]:
+            key, value = prop.split('=')
+            props[key] = value
+        for designator in designators:
+            classname, nodeid = roundupdb.splitDesignator(designator)
+            cl = self.db.getclass(classname)
+            properties = cl.getprops()
+            for key, value in props.items():
+                type =  properties[key]
+                if isinstance(type, hyperdb.String):
+                    continue
+                elif isinstance(type, hyperdb.Password):
+                    props[key] = password.Password(value)
+                elif isinstance(type, hyperdb.Date):
+                    props[key] = date.Date(value)
+                elif isinstance(type, hyperdb.Interval):
+                    props[key] = date.Interval(value)
+                elif isinstance(type, hyperdb.Link):
+                    props[key] = value
+                elif isinstance(type, hyperdb.Multilink):
+                    props[key] = value.split(',')
+            apply(cl.set, (nodeid, ), props)
+        return 0
+
+    def do_find(self, args):
+        '''Usage: find classname propname=value ...
+        Find the nodes of the given class with a given link property value.
+
+        Find the nodes of the given class with a given link property value. The
+        value may be either the nodeid of the linked node, or its key value.
+        '''
+        classname = args[0]
+        cl = self.db.getclass(classname)
+
+        # look up the linked-to class and get the nodeid that has the value
+        propname, value = args[1].split('=')
+        num_re = re.compile('^\d+$')
+        if not num_re.match(value):
+            propcl = cl.properties[propname]
+            if (not isinstance(propcl, hyperdb.Link) and not
+                    isinstance(type, hyperdb.Multilink)):
+                print 'You may only "find" link properties'
+                return 1
+            propcl = self.db.getclass(propcl.classname)
+            value = propcl.lookup(value)
+
+        # now do the find
+        if self.comma_sep:
+            print ','.join(cl.find(**{propname: value}))
         else:
-            print db.getclass(classname).get(nodeid, propname)
-    if comma_sep:
-        print ','.join(l)
-    return 0
-
-
-def do_set(db, args, comma_sep=0):
-    '''Usage: set designator[,designator]* propname=value ...
-    Set the given property of one or more designator(s).
-
-    Sets the property to the value for all designators given.
-    '''
-    from roundup import hyperdb
-
-    designators = string.split(args[0], ',')
-    props = {}
-    for prop in args[1:]:
-        key, value = prop.split('=')
-        props[key] = value
-    for designator in designators:
-        classname, nodeid = roundupdb.splitDesignator(designator)
-        cl = db.getclass(classname)
-        properties = cl.getprops()
-        for key, value in props.items():
+            print cl.find(**{propname: value})
+        return 0
+
+    def do_spec(self, args):
+        '''Usage: spec classname
+        Show the properties for a classname.
+
+        This lists the properties for a given class.
+        '''
+        classname = args[0]
+        cl = self.db.getclass(classname)
+        keyprop = cl.getkey()
+        for key, value in cl.properties.items():
+            if keyprop == key:
+                print '%s: %s (key property)'%(key, value)
+            else:
+                print '%s: %s'%(key, value)
+
+    def do_create(self, args):
+        '''Usage: create classname property=value ...
+        Create a new entry of a given class.
+
+        This creates a new entry of the given class using the property
+        name=value arguments provided on the command line after the "create"
+        command.
+        '''
+        from roundup import hyperdb
+
+        classname = args[0]
+        cl = self.db.getclass(classname)
+        props = {}
+        properties = cl.getprops(protected = 0)
+        if len(args) == 1:
+            # ask for the properties
+            for key, value in properties.items():
+                if key == 'id': continue
+                name = value.__class__.__name__
+                if isinstance(value , hyperdb.Password):
+                    again = None
+                    while value != again:
+                        value = getpass.getpass('%s (Password): '%key.capitalize())
+                        again = getpass.getpass('   %s (Again): '%key.capitalize())
+                        if value != again: print 'Sorry, try again...'
+                    if value:
+                        props[key] = value
+                else:
+                    value = raw_input('%s (%s): '%(key.capitalize(), name))
+                    if value:
+                        props[key] = value
+        else:
+            # use the args
+            for prop in args[1:]:
+                key, value = prop.split('=')
+                props[key] = value 
+
+        # convert types
+        for key in props.keys():
             type =  properties[key]
-            if isinstance(type, hyperdb.String):
-                continue
-            elif isinstance(type, hyperdb.Password):
-                props[key] = password.Password(value)
-            elif isinstance(type, hyperdb.Date):
+            if isinstance(type, hyperdb.Date):
                 props[key] = date.Date(value)
             elif isinstance(type, hyperdb.Interval):
                 props[key] = date.Interval(value)
-            elif isinstance(type, hyperdb.Link):
-                props[key] = value
+            elif isinstance(type, hyperdb.Password):
+                props[key] = password.Password(value)
             elif isinstance(type, hyperdb.Multilink):
                 props[key] = value.split(',')
-        apply(cl.set, (nodeid, ), props)
-    return 0
-
-def do_find(db, args, comma_sep=0):
-    '''Usage: find classname propname=value ...
-    Find the nodes of the given class with a given link property value.
-
-    Find the nodes of the given class with a given link property value. The
-    value may be either the nodeid of the linked node, or its key value.
-    '''
-    classname = args[0]
-    cl = db.getclass(classname)
-
-    # look up the linked-to class and get the nodeid that has the value
-    propname, value = args[1].split('=')
-    num_re = re.compile('^\d+$')
-    if not num_re.match(value):
-        propcl = cl.properties[propname]
-       if (not isinstance(propcl, hyperdb.Link) and not
-                isinstance(type, hyperdb.Multilink)):
-            print 'You may only "find" link properties'
-            return 1
-        propcl = db.getclass(propcl.classname)
-        value = propcl.lookup(value)
-
-    # now do the find
-    if comma_sep:
-        print ','.join(cl.find(**{propname: value}))
-    else:
-        print cl.find(**{propname: value})
-    return 0
-
-def do_spec(db, args, comma_sep=0):
-    '''Usage: spec classname
-    Show the properties for a classname.
-
-    This lists the properties for a given class.
-    '''
-    classname = args[0]
-    cl = db.getclass(classname)
-    keyprop = cl.getkey()
-    for key, value in cl.properties.items():
-        if keyprop == key:
-            print '%s: %s (key property)'%(key, value)
-        else:
-            print '%s: %s'%(key, value)
-
-def do_create(db, args, comma_sep=0):
-    '''Usage: create classname property=value ...
-    Create a new entry of a given class.
-
-    This creates a new entry of the given class using the property
-    name=value arguments provided on the command line after the "create"
-    command.
-    '''
-    from roundup import hyperdb
-
-    classname = args[0]
-    cl = db.getclass(classname)
-    props = {}
-    properties = cl.getprops(protected = 0)
-    if len(args) == 1:
-        # ask for the properties
-        for key, value in properties.items():
-            if key == 'id': continue
-            name = value.__class__.__name__
-            if isinstance(value , hyperdb.Password):
-                again = None
-                while value != again:
-                    value = getpass.getpass('%s (Password): '%key.capitalize())
-                    again = getpass.getpass('   %s (Again): '%key.capitalize())
-                    if value != again: print 'Sorry, try again...'
-                if value:
-                    props[key] = value
-            else:
-                value = raw_input('%s (%s): '%(key.capitalize(), name))
-                if value:
-                    props[key] = value
-    else:
-        # use the args
-        for prop in args[1:]:
-            key, value = prop.split('=')
-            props[key] = value 
-
-    # convert types
-    for key in props.keys():
-        type =  properties[key]
-        if isinstance(type, hyperdb.Date):
-            props[key] = date.Date(value)
-        elif isinstance(type, hyperdb.Interval):
-            props[key] = date.Interval(value)
-        elif isinstance(type, hyperdb.Password):
-            props[key] = password.Password(value)
-        elif isinstance(type, hyperdb.Multilink):
-            props[key] = value.split(',')
-
-    if cl.getkey() and not props.has_key(cl.getkey()):
-        print "You must provide the '%s' property."%cl.getkey()
-    else:
-        print apply(cl.create, (), props)
-
-    return 0
-
-def do_list(db, args, comma_sep=0):
-    '''Usage: list classname [property]
-    List the instances of a class.
-
-    Lists all instances of the given class. If the property is not
-    specified, the  "label" property is used. The label property is tried
-    in order: the key, "name", "title" and then the first property,
-    alphabetically.
-    '''
-    classname = args[0]
-    cl = db.getclass(classname)
-    if len(args) > 1:
-        key = args[1]
-    else:
-        key = cl.labelprop()
-    if comma_sep:
-        print ','.join(cl.list())
-    else:
-        for nodeid in cl.list():
-            value = cl.get(nodeid, key)
-            print "%4s: %s"%(nodeid, value)
-    return 0
-
-def do_table(db, args, comma_sep=None):
-    '''Usage: table classname [property[,property]*]
-    List the instances of a class in tabular form.
-
-    Lists all instances of the given class. If the properties are not
-    specified, all properties are displayed. By default, the column widths
-    are the width of the property names. The width may be explicitly defined
-    by defining the property as "name:width". For example::
-      roundup> table priority id,name:10
-      Id Name
-      1  fatal-bug 
-      2  bug       
-      3  usability 
-      4  feature   
-    '''
-    classname = args[0]
-    cl = db.getclass(classname)
-    if len(args) > 1:
-        prop_names = args[1].split(',')
-    else:
-        prop_names = cl.getprops().keys()
-    props = []
-    for name in prop_names:
-        if ':' in name:
-            name, width = name.split(':')
-            props.append((name, int(width)))
+
+        if cl.getkey() and not props.has_key(cl.getkey()):
+            print "You must provide the '%s' property."%cl.getkey()
         else:
-            props.append((name, len(name)))
+            print apply(cl.create, (), props)
 
-    print ' '.join([string.capitalize(name) for name, width in props])
-    for nodeid in cl.list():
-        l = []
-        for name, width in props:
-            if name != 'id':
-                value = str(cl.get(nodeid, name))
-            else:
-                value = str(nodeid)
-            f = '%%-%ds'%width
-            l.append(f%value[:width])
-        print ' '.join(l)
-    return 0
-
-def do_history(db, args, comma_sep=0):
-    '''Usage: history designator
-    Show the history entries of a designator.
-
-    Lists the journal entries for the node identified by the designator.
-    '''
-    classname, nodeid = roundupdb.splitDesignator(args[0])
-    # TODO: handle the -c option?
-    print db.getclass(classname).history(nodeid)
-    return 0
-
-def do_retire(db, args, comma_sep=0):
-    '''Usage: retire designator[,designator]*
-    Retire the node specified by designator.
-
-    This action indicates that a particular node is not to be retrieved by
-    the list or find commands, and its key value may be re-used.
-    '''
-    designators = string.split(args[0], ',')
-    for designator in designators:
-        classname, nodeid = roundupdb.splitDesignator(designator)
-        db.getclass(classname).retire(nodeid)
-    return 0
-
-def do_export(db, args, comma_sep=0):
-    '''Usage: export class[,class] destination_dir
-    Export the database to tab-separated-value files.
-
-    This action exports the current data from the database into
-    tab-separated-value files that are placed in the nominated destination
-    directory. The journals are not exported.
-    '''
-    if len(args) < 2:
-        print do_export.__doc__
-        return 1
-    classes = string.split(args[0], ',')
-    dir = args[1]
+        return 0
 
-    # use the csv parser if we can - it's faster
-    if csv is not None:
-        p = csv.parser(field_sep=':')
+    def do_list(self, args):
+        '''Usage: list classname [property]
+        List the instances of a class.
 
-    # do all the classes specified
-    for classname in classes:
-        cl = db.getclass(classname)
-        f = open(os.path.join(dir, classname+'.csv'), 'w')
-        f.write(string.join(cl.properties.keys(), ':') + '\n')
+        Lists all instances of the given class. If the property is not
+        specified, the  "label" property is used. The label property is tried
+        in order: the key, "name", "title" and then the first property,
+        alphabetically.
+        '''
+        classname = args[0]
+        cl = self.db.getclass(classname)
+        if len(args) > 1:
+            key = args[1]
+        else:
+            key = cl.labelprop()
+        if self.comma_sep:
+            print ','.join(cl.list())
+        else:
+            for nodeid in cl.list():
+                value = cl.get(nodeid, key)
+                print "%4s: %s"%(nodeid, value)
+        return 0
+
+    def do_table(self, args):
+        '''Usage: table classname [property[,property]*]
+        List the instances of a class in tabular form.
+
+        Lists all instances of the given class. If the properties are not
+        specified, all properties are displayed. By default, the column widths
+        are the width of the property names. The width may be explicitly defined
+        by defining the property as "name:width". For example::
+          roundup> table priority id,name:10
+          Id Name
+          1  fatal-bug 
+          2  bug       
+          3  usability 
+          4  feature   
+        '''
+        classname = args[0]
+        cl = self.db.getclass(classname)
+        if len(args) > 1:
+            prop_names = args[1].split(',')
+        else:
+            prop_names = cl.getprops().keys()
+        props = []
+        for name in prop_names:
+            if ':' in name:
+                name, width = name.split(':')
+                props.append((name, int(width)))
+            else:
+                props.append((name, len(name)))
 
-        # all nodes for this class
-        properties = cl.properties.items()
+        print ' '.join([string.capitalize(name) for name, width in props])
         for nodeid in cl.list():
             l = []
-            for prop, type in properties:
-                value = cl.get(nodeid, prop)
-                # convert data where needed
-                if isinstance(type, hyperdb.Date):
-                    value = value.get_tuple()
-                elif isinstance(type, hyperdb.Interval):
-                    value = value.get_tuple()
-                elif isinstance(type, hyperdb.Password):
-                    value = str(value)
-                l.append(repr(value))
+            for name, width in props:
+                if name != 'id':
+                    value = str(cl.get(nodeid, name))
+                else:
+                    value = str(nodeid)
+                f = '%%-%ds'%width
+                l.append(f%value[:width])
+            print ' '.join(l)
+        return 0
+
+    def do_history(self, args):
+        '''Usage: history designator
+        Show the history entries of a designator.
+
+        Lists the journal entries for the node identified by the designator.
+        '''
+        classname, nodeid = roundupdb.splitDesignator(args[0])
+        # TODO: handle the -c option?
+        print self.db.getclass(classname).history(nodeid)
+        return 0
 
-                       # now write
-            if csv is not None:
-               f.write(p.join(l) + '\n')
-            else:
-               # escape the individual entries to they're valid CSV
-               m = []
-               for entry in l:
-                  if '"' in entry:
-                      entry = '""'.join(entry.split('"'))
-                  if ':' in entry:
-                      entry = '"%s"'%entry
-                  m.append(entry)
-               f.write(':'.join(m) + '\n')
-    return 0
-
-def do_import(db, args, comma_sep=0):
-    '''Usage: import class file
-    Import the contents of the tab-separated-value file.
-
-    The file must define the same properties as the class (including having
-    a "header" line with those property names.) The new nodes are added to
-    the existing database - if you want to create a new database using the
-    imported data, then create a new database (or, tediously, retire all
-    the old data.)
-    '''
-    if len(args) < 2:
-        print do_import.__doc__
-        return 1
-    if csv is None:
-        print 'Sorry, you need the csv module to use this function.'
-        print 'Get it from: http://www.object-craft.com.au/projects/csv/'
-        return 1
+    def do_retire(self, args):
+        '''Usage: retire designator[,designator]*
+        Retire the node specified by designator.
 
-    from roundup import hyperdb
-
-    # ensure that the properties and the CSV file headings match
-    cl = db.getclass(args[0])
-    f = open(args[1])
-    p = csv.parser(field_sep=':')
-    file_props = p.parse(f.readline())
-    props = cl.properties.keys()
-    m = file_props[:]
-    m.sort()
-    props.sort()
-    if m != props:
-        print 'Import file doesn\'t define the same properties as "%s".'%args[0]
-        return 1
+        This action indicates that a particular node is not to be retrieved by
+        the list or find commands, and its key value may be re-used.
+        '''
+        designators = string.split(args[0], ',')
+        for designator in designators:
+            classname, nodeid = roundupdb.splitDesignator(designator)
+            self.db.getclass(classname).retire(nodeid)
+        return 0
+
+    def do_export(self, args):
+        '''Usage: export class[,class] destination_dir
+        Export the database to tab-separated-value files.
+
+        This action exports the current data from the database into
+        tab-separated-value files that are placed in the nominated destination
+        directory. The journals are not exported.
+        '''
+        if len(args) < 2:
+            print do_export.__doc__
+            return 1
+        classes = string.split(args[0], ',')
+        dir = args[1]
+
+        # use the csv parser if we can - it's faster
+        if csv is not None:
+            p = csv.parser(field_sep=':')
+
+        # do all the classes specified
+        for classname in classes:
+            cl = self.db.getclass(classname)
+            f = open(os.path.join(dir, classname+'.csv'), 'w')
+            f.write(string.join(cl.properties.keys(), ':') + '\n')
+
+            # all nodes for this class
+            properties = cl.properties.items()
+            for nodeid in cl.list():
+                l = []
+                for prop, type in properties:
+                    value = cl.get(nodeid, prop)
+                    # convert data where needed
+                    if isinstance(type, hyperdb.Date):
+                        value = value.get_tuple()
+                    elif isinstance(type, hyperdb.Interval):
+                        value = value.get_tuple()
+                    elif isinstance(type, hyperdb.Password):
+                        value = str(value)
+                    l.append(repr(value))
+
+                # now write
+                if csv is not None:
+                   f.write(p.join(l) + '\n')
+                else:
+                   # escape the individual entries to they're valid CSV
+                   m = []
+                   for entry in l:
+                      if '"' in entry:
+                          entry = '""'.join(entry.split('"'))
+                      if ':' in entry:
+                          entry = '"%s"'%entry
+                      m.append(entry)
+                   f.write(':'.join(m) + '\n')
+        return 0
+
+    def do_import(self, args):
+        '''Usage: import class file
+        Import the contents of the tab-separated-value file.
+
+        The file must define the same properties as the class (including having
+        a "header" line with those property names.) The new nodes are added to
+        the existing database - if you want to create a new database using the
+        imported data, then create a new database (or, tediously, retire all
+        the old data.)
+        '''
+        if len(args) < 2:
+            print do_import.__doc__
+            return 1
+        if csv is None:
+            print 'Sorry, you need the csv module to use this function.'
+            print 'Get it from: http://www.object-craft.com.au/projects/csv/'
+            return 1
 
-    # loop through the file and create a node for each entry
-    n = range(len(props))
-    while 1:
-        line = f.readline()
-        if not line: break
+        from roundup import hyperdb
 
-        # parse lines until we get a complete entry
+        # ensure that the properties and the CSV file headings match
+        cl = self.db.getclass(args[0])
+        f = open(args[1])
+        p = csv.parser(field_sep=':')
+        file_props = p.parse(f.readline())
+        props = cl.properties.keys()
+        m = file_props[:]
+        m.sort()
+        props.sort()
+        if m != props:
+            print 'Import file doesn\'t define the same properties as "%s".'%args[0]
+            return 1
+
+        # loop through the file and create a node for each entry
+        n = range(len(props))
         while 1:
-            l = p.parse(line)
-            if l: break
-
-        # make the new node's property map
-        d = {}
-        for i in n:
-            # Use eval to reverse the repr() used to output the CSV
-            value = eval(l[i])
-            # Figure the property for this column
-            key = file_props[i]
-            type = cl.properties[key]
-            # Convert for property type
-            if isinstance(type, hyperdb.Date):
-                value = date.Date(value)
-            elif isinstance(type, hyperdb.Interval):
-                value = date.Interval(value)
-            elif isinstance(type, hyperdb.Password):
-                pwd = password.Password()
-                pwd.unpack(value)
-                value = pwd
-            if value is not None:
-                d[key] = value
-
-        # and create the new node
-        apply(cl.create, (), d)
-    return 0
-
-def figureCommands():
-    d = {}
-    for k, v in globals().items():
-        if k[:3] == 'do_':
-            d[k[3:]] = v
-    return d
-
-def figureHelp():
-    d = {}
-    for k, v in globals().items():
-        if k[:5] == 'help_':
-            d[k[5:]] = v
-    return d
+            line = f.readline()
+            if not line: break
+
+            # parse lines until we get a complete entry
+            while 1:
+                l = p.parse(line)
+                if l: break
+
+            # make the new node's property map
+            d = {}
+            for i in n:
+                # Use eval to reverse the repr() used to output the CSV
+                value = eval(l[i])
+                # Figure the property for this column
+                key = file_props[i]
+                type = cl.properties[key]
+                # Convert for property type
+                if isinstance(type, hyperdb.Date):
+                    value = date.Date(value)
+                elif isinstance(type, hyperdb.Interval):
+                    value = date.Interval(value)
+                elif isinstance(type, hyperdb.Password):
+                    pwd = password.Password()
+                    pwd.unpack(value)
+                    value = pwd
+                if value is not None:
+                    d[key] = value
+
+            # and create the new node
+            apply(cl.create, (), d)
+        return 0
 
-class AdminTool:
     def run_command(self, args):
         '''Run a single command
         '''
@@ -567,14 +575,14 @@ class AdminTool:
         # handle help now
         if command == 'help':
             if len(args)>1:
-                do_help(args[1:])
+                self.do_help(args[1:])
                 return 0
-            do_help(['help'])
+            self.do_help(['help'])
             return 0
         if command == 'morehelp':
-            do_help(['help'])
-            help_commands()
-            help_all()
+            self.do_help(['help'])
+            self.help_commands()
+            self.help_all()
             return 0
 
         # make sure we have an instance_home
@@ -583,9 +591,9 @@ class AdminTool:
 
         # before we open the db, we may be doing an init
         if command == 'init':
-            return do_init(self.instance_home, args)
+            return self.do_init(self.instance_home, args)
 
-        function = figureCommands().get(command, None)
+        function = self.commands.get(command, None)
 
         # not a valid command
         if function is None:
@@ -594,7 +602,7 @@ class AdminTool:
 
         # get the instance
         instance = roundup.instance.open(self.instance_home)
-        db = instance.open('admin')
+        self.db = instance.open('admin')
 
         if len(args) < 2:
             print function.__doc__
@@ -602,9 +610,9 @@ class AdminTool:
 
         # do the command
         try:
-            return function(db, args[1:], comma_sep=self.comma_sep)
+            return function(args[1:])
         finally:
-            db.close()
+            self.db.close()
 
         return 1
 
@@ -663,6 +671,12 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.33  2001/10/17 23:13:19  richard
+# Did a fair bit of work on the admin tool. Now has an extra command "table"
+# which displays node information in a tabular format. Also fixed import and
+# export so they work. Removed freshen.
+# Fixed quopri usage in mailgw from bug reports.
+#
 # Revision 1.32  2001/10/17 06:57:29  richard
 # Interactive startup blurb - need to figure how to get the version in there.
 #