Skip to content

Commit c559531

Browse files
author
Richard Jones
committed
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.
1 parent 9aba428 commit c559531

File tree

2 files changed

+97
-56
lines changed

2 files changed

+97
-56
lines changed

roundup-admin

Lines changed: 86 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#! /usr/bin/python
1+
#! /Users/builder/bin/python
22
#
33
# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
44
# This module is free software, and you may redistribute it and/or modify
@@ -16,7 +16,7 @@
1616
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1717
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1818
#
19-
# $Id: roundup-admin,v 1.32 2001-10-17 06:57:29 richard Exp $
19+
# $Id: roundup-admin,v 1.33 2001-10-17 23:13:19 richard Exp $
2020

2121
import sys
2222
if int(sys.version[0]) < 2:
@@ -28,7 +28,7 @@ try:
2828
import csv
2929
except ImportError:
3030
csv = None
31-
from roundup import date, roundupdb, init, password
31+
from roundup import date, hyperdb, roundupdb, init, password
3232
import roundup.instance
3333

3434
def usage(message=''):
@@ -337,7 +337,7 @@ def do_list(db, args, comma_sep=0):
337337
'''Usage: list classname [property]
338338
List the instances of a class.
339339
340-
Lists all instances of the given class along. If the property is not
340+
Lists all instances of the given class. If the property is not
341341
specified, the "label" property is used. The label property is tried
342342
in order: the key, "name", "title" and then the first property,
343343
alphabetically.
@@ -356,6 +356,48 @@ def do_list(db, args, comma_sep=0):
356356
print "%4s: %s"%(nodeid, value)
357357
return 0
358358

359+
def do_table(db, args, comma_sep=None):
360+
'''Usage: table classname [property[,property]*]
361+
List the instances of a class in tabular form.
362+
363+
Lists all instances of the given class. If the properties are not
364+
specified, all properties are displayed. By default, the column widths
365+
are the width of the property names. The width may be explicitly defined
366+
by defining the property as "name:width". For example::
367+
roundup> table priority id,name:10
368+
Id Name
369+
1 fatal-bug
370+
2 bug
371+
3 usability
372+
4 feature
373+
'''
374+
classname = args[0]
375+
cl = db.getclass(classname)
376+
if len(args) > 1:
377+
prop_names = args[1].split(',')
378+
else:
379+
prop_names = cl.getprops().keys()
380+
props = []
381+
for name in prop_names:
382+
if ':' in name:
383+
name, width = name.split(':')
384+
props.append((name, int(width)))
385+
else:
386+
props.append((name, len(name)))
387+
388+
print ' '.join([string.capitalize(name) for name, width in props])
389+
for nodeid in cl.list():
390+
l = []
391+
for name, width in props:
392+
if name != 'id':
393+
value = str(cl.get(nodeid, name))
394+
else:
395+
value = str(nodeid)
396+
f = '%%-%ds'%width
397+
l.append(f%value[:width])
398+
print ' '.join(l)
399+
return 0
400+
359401
def do_history(db, args, comma_sep=0):
360402
'''Usage: history designator
361403
Show the history entries of a designator.
@@ -382,11 +424,10 @@ def do_retire(db, args, comma_sep=0):
382424

383425
def do_export(db, args, comma_sep=0):
384426
'''Usage: export class[,class] destination_dir
385-
** EXPERIMENTAL **
386-
Export the database to CSV files by class in the given directory.
427+
Export the database to tab-separated-value files.
387428
388429
This action exports the current data from the database into
389-
comma-separated files that are placed in the nominated destination
430+
tab-separated-value files that are placed in the nominated destination
390431
directory. The journals are not exported.
391432
'''
392433
if len(args) < 2:
@@ -397,35 +438,47 @@ def do_export(db, args, comma_sep=0):
397438

398439
# use the csv parser if we can - it's faster
399440
if csv is not None:
400-
p = csv.parser()
441+
p = csv.parser(field_sep=':')
401442

402443
# do all the classes specified
403444
for classname in classes:
404445
cl = db.getclass(classname)
405446
f = open(os.path.join(dir, classname+'.csv'), 'w')
406-
f.write(string.join(cl.properties.keys(), ',') + '\n')
447+
f.write(string.join(cl.properties.keys(), ':') + '\n')
407448

408449
# all nodes for this class
450+
properties = cl.properties.items()
409451
for nodeid in cl.list():
452+
l = []
453+
for prop, type in properties:
454+
value = cl.get(nodeid, prop)
455+
# convert data where needed
456+
if isinstance(type, hyperdb.Date):
457+
value = value.get_tuple()
458+
elif isinstance(type, hyperdb.Interval):
459+
value = value.get_tuple()
460+
elif isinstance(type, hyperdb.Password):
461+
value = str(value)
462+
l.append(repr(value))
463+
464+
# now write
410465
if csv is not None:
411-
s = p.join(map(str, cl.getnode(nodeid).values(protected=0)))
412-
f.write(s + '\n')
466+
f.write(p.join(l) + '\n')
413467
else:
414-
l = []
415468
# escape the individual entries to they're valid CSV
416-
for entry in map(str, cl.getnode(nodeid).values(protected=0)):
469+
m = []
470+
for entry in l:
417471
if '"' in entry:
418472
entry = '""'.join(entry.split('"'))
419-
if ',' in entry:
473+
if ':' in entry:
420474
entry = '"%s"'%entry
421-
l.append(entry)
422-
f.write(','.join(l) + '\n')
475+
m.append(entry)
476+
f.write(':'.join(m) + '\n')
423477
return 0
424478

425479
def do_import(db, args, comma_sep=0):
426480
'''Usage: import class file
427-
** EXPERIMENTAL **
428-
Import the contents of the CSV file as new nodes for the given class.
481+
Import the contents of the tab-separated-value file.
429482
430483
The file must define the same properties as the class (including having
431484
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):
434487
the old data.)
435488
'''
436489
if len(args) < 2:
437-
print do_export.__doc__
490+
print do_import.__doc__
438491
return 1
439492
if csv is None:
440493
print 'Sorry, you need the csv module to use this function.'
@@ -446,15 +499,14 @@ def do_import(db, args, comma_sep=0):
446499
# ensure that the properties and the CSV file headings match
447500
cl = db.getclass(args[0])
448501
f = open(args[1])
449-
p = csv.parser()
502+
p = csv.parser(field_sep=':')
450503
file_props = p.parse(f.readline())
451504
props = cl.properties.keys()
452505
m = file_props[:]
453506
m.sort()
454507
props.sort()
455508
if m != props:
456-
print do_export.__doc__
457-
print "\n\nFile doesn't define the same properties"
509+
print 'Import file doesn\'t define the same properties as "%s".'%args[0]
458510
return 1
459511

460512
# loop through the file and create a node for each entry
@@ -471,9 +523,12 @@ def do_import(db, args, comma_sep=0):
471523
# make the new node's property map
472524
d = {}
473525
for i in n:
474-
value = l[i]
526+
# Use eval to reverse the repr() used to output the CSV
527+
value = eval(l[i])
528+
# Figure the property for this column
475529
key = file_props[i]
476530
type = cl.properties[key]
531+
# Convert for property type
477532
if isinstance(type, hyperdb.Date):
478533
value = date.Date(value)
479534
elif isinstance(type, hyperdb.Interval):
@@ -482,37 +537,13 @@ def do_import(db, args, comma_sep=0):
482537
pwd = password.Password()
483538
pwd.unpack(value)
484539
value = pwd
485-
elif isinstance(type, hyperdb.Multilink):
486-
value = value.split(',')
487-
d[key] = value
540+
if value is not None:
541+
d[key] = value
488542

489543
# and create the new node
490544
apply(cl.create, (), d)
491545
return 0
492546

493-
def do_freshen(db, args, comma_sep=0):
494-
'''Usage: freshen
495-
Freshen an existing instance. **DO NOT USE**
496-
497-
This currently kills databases!!!!
498-
499-
This action should generally not be used. It reads in an instance
500-
database and writes it again. In the future, is may also update
501-
instance code to account for changes in templates. It's probably wise
502-
not to use it anyway. Until we're sure it won't break things...
503-
'''
504-
# for classname, cl in db.classes.items():
505-
# properties = cl.properties.items()
506-
# for nodeid in cl.list():
507-
# node = {}
508-
# for name, type in properties:
509-
# isinstance( if type, hyperdb.Multilink):
510-
# node[name] = cl.get(nodeid, name, [])
511-
# else:
512-
# node[name] = cl.get(nodeid, name, None)
513-
# db.setnode(classname, nodeid, node)
514-
return 1
515-
516547
def figureCommands():
517548
d = {}
518549
for k, v in globals().items():
@@ -528,8 +559,9 @@ def figureHelp():
528559
return d
529560

530561
class AdminTool:
531-
532562
def run_command(self, args):
563+
'''Run a single command
564+
'''
533565
command = args[0]
534566

535567
# handle help now
@@ -557,7 +589,7 @@ class AdminTool:
557589

558590
# not a valid command
559591
if function is None:
560-
usage('Unknown command "%s"'%command)
592+
print 'Unknown command "%s" ("help commands" for a list)'%command
561593
return 1
562594

563595
# get the instance
@@ -631,6 +663,9 @@ if __name__ == '__main__':
631663

632664
#
633665
# $Log: not supported by cvs2svn $
666+
# Revision 1.32 2001/10/17 06:57:29 richard
667+
# Interactive startup blurb - need to figure how to get the version in there.
668+
#
634669
# Revision 1.31 2001/10/17 06:17:26 richard
635670
# Now with readline support :)
636671
#

roundup/mailgw.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ class node. Any parts of other types are each stored in separate files
7272
an exception, the original message is bounced back to the sender with the
7373
explanatory message given in the exception.
7474
75-
$Id: mailgw.py,v 1.19 2001-10-11 23:43:04 richard Exp $
75+
$Id: mailgw.py,v 1.20 2001-10-17 23:13:19 richard Exp $
7676
'''
7777

7878

@@ -290,14 +290,16 @@ def handle_message(self, message):
290290
# try name on Content-Type
291291
name = part.getparam('name')
292292
# this is just an attachment
293-
data = part.fp.read()
294293
encoding = part.getencoding()
295294
if encoding == 'base64':
296-
data = binascii.a2b_base64(data)
295+
data = binascii.a2b_base64(part.fp.read())
297296
elif encoding == 'quoted-printable':
298-
data = quopri.decode(data)
297+
# the quopri module wants to work with files
298+
decoded = cStringIO.StringIO()
299+
quopri.decode(part.fp, decoded)
300+
data = decoded.getvalue()
299301
elif encoding == 'uuencoded':
300-
data = binascii.a2b_uu(data)
302+
data = binascii.a2b_uu(part.fp.read())
301303
attachments.append((name, part.gettype(), data))
302304

303305
if content is None:
@@ -416,6 +418,10 @@ def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
416418

417419
#
418420
# $Log: not supported by cvs2svn $
421+
# Revision 1.19 2001/10/11 23:43:04 richard
422+
# Implemented the comma-separated printing option in the admin tool.
423+
# Fixed a typo (more of a vim-o actually :) in mailgw.
424+
#
419425
# Revision 1.18 2001/10/11 06:38:57 richard
420426
# Initial cut at trying to handle people responding to CC'ed messages that
421427
# create an issue.

0 commit comments

Comments
 (0)