Code

Did a fair bit of work on the admin tool. Now has an extra command "table"
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 17 Oct 2001 23:13:19 +0000 (23:13 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 17 Oct 2001 23:13:19 +0000 (23:13 +0000)
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.

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

roundup-admin
roundup/mailgw.py

index be494742298c4a50378bce1d28a01e76bf2f87d5..f7a0ff270e1f988cff590b2e4960d32c54788618 100755 (executable)
@@ -1,4 +1,4 @@
-#! /usr/bin/python
+#! /Users/builder/bin/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.32 2001-10-17 06:57:29 richard Exp $
+# $Id: roundup-admin,v 1.33 2001-10-17 23:13:19 richard Exp $
 
 import sys
 if int(sys.version[0]) < 2:
@@ -28,7 +28,7 @@ try:
     import csv
 except ImportError:
     csv = None
-from roundup import date, roundupdb, init, password
+from roundup import date, hyperdb, roundupdb, init, password
 import roundup.instance
 
 def usage(message=''):
@@ -337,7 +337,7 @@ 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 along. If the property is not
+    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.
@@ -356,6 +356,48 @@ def do_list(db, args, comma_sep=0):
             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)))
+        else:
+            props.append((name, len(name)))
+
+    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.
@@ -382,11 +424,10 @@ def do_retire(db, args, comma_sep=0):
 
 def do_export(db, args, comma_sep=0):
     '''Usage: export class[,class] destination_dir
-    ** EXPERIMENTAL **
-    Export the database to CSV files by class in the given directory.
+    Export the database to tab-separated-value files.
 
     This action exports the current data from the database into
-    comma-separated files that are placed in the nominated destination
+    tab-separated-value files that are placed in the nominated destination
     directory. The journals are not exported.
     '''
     if len(args) < 2:
@@ -397,35 +438,47 @@ def do_export(db, args, comma_sep=0):
 
     # use the csv parser if we can - it's faster
     if csv is not None:
-        p = csv.parser()
+        p = csv.parser(field_sep=':')
 
     # 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')
+        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:
-               s = p.join(map(str, cl.getnode(nodeid).values(protected=0)))
-               f.write(s + '\n')
+               f.write(p.join(l) + '\n')
             else:
-               l = []
                # escape the individual entries to they're valid CSV
-               for entry in map(str, cl.getnode(nodeid).values(protected=0)):
+               m = []
+               for entry in l:
                   if '"' in entry:
                       entry = '""'.join(entry.split('"'))
-                  if ',' in entry:
+                  if ':' in entry:
                       entry = '"%s"'%entry
-                  l.append(entry)
-               f.write(','.join(l) + '\n')
+                  m.append(entry)
+               f.write(':'.join(m) + '\n')
     return 0
 
 def do_import(db, args, comma_sep=0):
     '''Usage: import class file
-    ** EXPERIMENTAL **
-    Import the contents of the CSV file as new nodes for the given class.
+    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
@@ -434,7 +487,7 @@ def do_import(db, args, comma_sep=0):
     the old data.)
     '''
     if len(args) < 2:
-        print do_export.__doc__
+        print do_import.__doc__
         return 1
     if csv is None:
         print 'Sorry, you need the csv module to use this function.'
@@ -446,15 +499,14 @@ def do_import(db, args, comma_sep=0):
     # ensure that the properties and the CSV file headings match
     cl = db.getclass(args[0])
     f = open(args[1])
-    p = csv.parser()
+    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 do_export.__doc__
-        print "\n\nFile doesn't define the same properties"
+        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
@@ -471,9 +523,12 @@ def do_import(db, args, comma_sep=0):
         # make the new node's property map
         d = {}
         for i in n:
-            value = l[i]
+            # 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):
@@ -482,37 +537,13 @@ def do_import(db, args, comma_sep=0):
                 pwd = password.Password()
                 pwd.unpack(value)
                 value = pwd
-            elif isinstance(type, hyperdb.Multilink):
-                value = value.split(',')
-            d[key] = value
+            if value is not None:
+                d[key] = value
 
         # and create the new node
         apply(cl.create, (), d)
     return 0
 
-def do_freshen(db, args, comma_sep=0):
-    '''Usage: freshen
-    Freshen an existing instance.  **DO NOT USE**
-
-    This currently kills databases!!!!
-
-    This action should generally not be used. It reads in an instance
-    database and writes it again. In the future, is may also update
-    instance code to account for changes in templates. It's probably wise
-    not to use it anyway. Until we're sure it won't break things...
-    '''
-#    for classname, cl in db.classes.items():
-#        properties = cl.properties.items()
-#        for nodeid in cl.list():
-#            node = {}
-#            for name, type in properties:
-# isinstance(               if type, hyperdb.Multilink):
-#                    node[name] = cl.get(nodeid, name, [])
-#                else:
-#                    node[name] = cl.get(nodeid, name, None)
-#                db.setnode(classname, nodeid, node)
-    return 1
-
 def figureCommands():
     d = {}
     for k, v in globals().items():
@@ -528,8 +559,9 @@ def figureHelp():
     return d
 
 class AdminTool:
-
     def run_command(self, args):
+        '''Run a single command
+        '''
         command = args[0]
 
         # handle help now
@@ -557,7 +589,7 @@ class AdminTool:
 
         # not a valid command
         if function is None:
-            usage('Unknown command "%s"'%command)
+            print 'Unknown command "%s" ("help commands" for a list)'%command
             return 1
 
         # get the instance
@@ -631,6 +663,9 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.32  2001/10/17 06:57:29  richard
+# Interactive startup blurb - need to figure how to get the version in there.
+#
 # Revision 1.31  2001/10/17 06:17:26  richard
 # Now with readline support :)
 #
index 702fd559de9ebf610c6ce2eb1bebdb0f7e0ab384..88fe1e73c31c9418b8a9c9067854a2410df29b5e 100644 (file)
@@ -72,7 +72,7 @@ are calling the create() method to create a new node). If an auditor raises
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
-$Id: mailgw.py,v 1.19 2001-10-11 23:43:04 richard Exp $
+$Id: mailgw.py,v 1.20 2001-10-17 23:13:19 richard Exp $
 '''
 
 
@@ -290,14 +290,16 @@ Subject was: "%s"
                     # try name on Content-Type
                     name = part.getparam('name')
                     # this is just an attachment
-                    data = part.fp.read()
                     encoding = part.getencoding()
                     if encoding == 'base64':
-                        data = binascii.a2b_base64(data)
+                        data = binascii.a2b_base64(part.fp.read())
                     elif encoding == 'quoted-printable':
-                        data = quopri.decode(data)
+                        # the quopri module wants to work with files
+                        decoded = cStringIO.StringIO()
+                        quopri.decode(part.fp, decoded)
+                        data = decoded.getvalue()
                     elif encoding == 'uuencoded':
-                        data = binascii.a2b_uu(data)
+                        data = binascii.a2b_uu(part.fp.read())
                     attachments.append((name, part.gettype(), data))
 
             if content is None:
@@ -416,6 +418,10 @@ def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.19  2001/10/11 23:43:04  richard
+# Implemented the comma-separated printing option in the admin tool.
+# Fixed a typo (more of a vim-o actually :) in mailgw.
+#
 # Revision 1.18  2001/10/11 06:38:57  richard
 # Initial cut at trying to handle people responding to CC'ed messages that
 # create an issue.