481 lines (423 with data), 15.8 kB
#! /usr/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
# under the same terms as Python, so long as this copyright message and
# disclaimer are retained in their original form.
#
# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
# $Id: roundup-admin,v 1.15 2001-08-07 00:24:42 richard Exp $
import sys
if int(sys.version[0]) < 2:
print 'Roundup requires python 2.0 or later.'
sys.exit(1)
import string, os, getpass, getopt, re
from roundup import date, roundupdb, init
import roundup.instance
def usage(message=''):
if message: message = 'Problem: '+message+'\n'
commands = []
for command in figureCommands().values():
h = command.__doc__.split('\n')[0]
commands.append(h[7:])
commands.sort()
print '''%sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments>
Commands:
%s
Help:
roundup-admin -h
roundup-admin help
-- this help
roundup-admin help <command>
-- command-specific help
roundup-admin morehelp
-- even more detailed help
'''%(message, '\n '.join(commands))
def moreusage(message=''):
usage(message)
print '''
All commands (except help) require an instance specifier. This is just the path
to the roundup instance you're working with. It may be specified in the
environment variable ROUNDUP_INSTANCE or on the command line as "-i instance".
A designator is a classname and a nodeid concatenated, eg. bug1, user10, ...
Property values are represented as strings in command arguments and in the
printed results:
. Strings are, well, strings.
. Date values are printed in the full date format in the local time zone, and
accepted in the full format or any of the partial formats explained below.
. Link values are printed as node designators. When given as an argument,
node designators and key strings are both accepted.
. Multilink values are printed as lists of node designators joined by commas.
When given as an argument, node designators and key strings are both
accepted; an empty string, a single node, or a list of nodes joined by
commas is accepted.
When multiple nodes are specified to the roundup get or roundup set
commands, the specified properties are retrieved or set on all the listed
nodes.
When multiple results are returned by the roundup get or roundup find
commands, they are printed one per line (default) or joined by commas (with
the -c) option.
Where the command changes data, a login name/password is required. The
login may be specified as either "name" or "name:password".
. ROUNDUP_LOGIN environment variable
. the -u command-line option
If either the name or password is not supplied, they are obtained from the
command-line.
Date format examples:
"2000-04-17.03:45" means <Date 2000-04-17.08:45:00>
"2000-04-17" means <Date 2000-04-17.00:00:00>
"01-25" means <Date yyyy-01-25.00:00:00>
"08-13.22:13" means <Date yyyy-08-14.03:13:00>
"11-07.09:32:43" means <Date yyyy-11-07.14:32:43>
"14:25" means <Date yyyy-mm-dd.19:25:00>
"8:47:11" means <Date yyyy-mm-dd.13:47:11>
"." means "right now"
Command help:
'''
for name, command in figureCommands().items():
print '%s:'%name
print ' ',command.__doc__
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.
'''
# 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 [classic]: ').strip()
if not template:
template = 'classic'
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(db, 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.
'''
designators = string.split(args[0], ',')
propname = args[1]
# TODO: handle the -c option
for designator in designators:
classname, nodeid = roundupdb.splitDesignator(designator)
print db.getclass(classname).get(nodeid, propname)
return 0
def do_set(db, 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.
'''
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():
type = properties[key]
if type.isStringType:
continue
elif type.isDateType:
props[key] = date.Date(value)
elif type.isIntervalType:
props[key] = date.Interval(value)
elif type.isLinkType:
props[key] = value
elif type.isMultilinkType:
props[key] = value.split(',')
apply(cl.set, (nodeid, ), props)
return 0
def do_find(db, args):
'''Usage: find classname propname=value ...
Find the nodes of the given class with a given property value.
Find the nodes of the given class with a given 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 num_re.match(value):
nodeid = value
else:
propcl = cl.properties[propname].classname
propcl = db.getclass(propcl)
nodeid = propcl.lookup(value)
# now do the find
# TODO: handle the -c option
print cl.find(**{propname: nodeid})
return 0
def do_spec(db, args):
'''Usage: spec classname
Show the properties for a classname.
This lists the properties for a given class.
'''
classname = args[0]
cl = db.getclass(classname)
for key, value in cl.properties.items():
print '%s: %s'%(key, value)
def do_create(db, 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.
'''
classname = args[0]
cl = db.getclass(classname)
props = {}
properties = cl.getprops()
for prop in args[1:]:
key, value = prop.split('=')
type = properties[key]
if type.isStringType:
props[key] = value
elif type.isDateType:
props[key] = date.Date(value)
elif type.isIntervalType:
props[key] = date.Interval(value)
elif type.isLinkType:
props[key] = value
elif type.isMultilinkType:
props[key] = value.split(',')
print apply(cl.create, (), props)
return 0
def do_list(db, args):
'''Usage: list classname [property]
List the instances of a class.
Lists all instances of the given class along. 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()
# TODO: handle the -c option
for nodeid in cl.list():
value = cl.get(nodeid, key)
print "%4s: %s"%(nodeid, value)
return 0
def do_history(db, 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 db.getclass(classname).history(nodeid)
return 0
def do_retire(db, args):
'''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_freshen(db, args):
'''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:
# if type.isMultilinkType:
# 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():
if k[:3] == 'do_':
d[k[3:]] = v
return d
def printInitOptions():
import roundup.templates
templates = roundup.templates.listTemplates()
print 'Templates:', ', '.join(templates)
import roundup.backends
backends = roundup.backends.__all__
print 'Back ends:', ', '.join(backends)
def main():
opts, args = getopt.getopt(sys.argv[1:], 'i:u:hc')
# handle command-line args
instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
name = password = ''
if os.environ.has_key('ROUNDUP_LOGIN'):
l = os.environ['ROUNDUP_LOGIN'].split(':')
name = l[0]
if len(l) > 1:
password = l[1]
comma_sep = 0
for opt, arg in opts:
if opt == '-h':
usage()
return 0
if opt == '-i':
instance_home = arg
if opt == '-u':
l = arg.split(':')
name = l[0]
if len(l) > 1:
password = l[1]
if opt == '-c':
comma_sep = 1
# figure the command
if not args:
usage('No command specified')
return 1
command = args[0]
# handle help now
if command == 'help':
if len(args)>1:
command = figureCommands().get(args[1], None)
if not command:
usage('no such command "%s"'%args[1])
return 1
print command.__doc__
if args[1] == 'init':
printInitOptions()
return 0
usage()
return 0
if command == 'morehelp':
moreusage()
return 0
# make sure we have an instance_home
while not instance_home:
instance_home = raw_input('Enter instance home: ').strip()
# before we open the db, we may be doing an init
if command == 'init':
return do_init(instance_home, args)
# open the database
if command in ('create', 'set', 'retire', 'freshen'):
while not name:
name = raw_input('Login name: ')
while not password:
password = getpass.getpass(' password: ')
# get the instance
instance = roundup.instance.open(instance_home)
function = figureCommands().get(command, None)
# not a valid command
if function is None:
usage('Unknown command "%s"'%command)
return 1
db = instance.open(name or 'admin')
try:
return function(db, args[1:])
finally:
db.close()
return 1
if __name__ == '__main__':
sys.exit(main())
#
# $Log: not supported by cvs2svn $
# Revision 1.14 2001/08/07 00:15:51 richard
# Added the copyright/license notice to (nearly) all files at request of
# Bizar Software.
#
# Revision 1.13 2001/08/05 07:44:13 richard
# Instances are now opened by a special function that generates a unique
# module name for the instances on import time.
#
# Revision 1.12 2001/08/03 01:28:33 richard
# Used the much nicer load_package, pointed out by Steve Majewski.
#
# Revision 1.11 2001/08/03 00:59:34 richard
# Instance import now imports the instance using imp.load_module so that
# we can have instance homes of "roundup" or other existing python package
# names.
#
# Revision 1.10 2001/07/30 08:12:17 richard
# Added time logging and file uploading to the templates.
#
# Revision 1.9 2001/07/30 03:52:55 richard
# init help now lists templates and backends
#
# Revision 1.8 2001/07/30 02:37:07 richard
# Freshen is really broken. Commented out.
#
# Revision 1.7 2001/07/30 01:28:46 richard
# Bugfixes
#
# Revision 1.6 2001/07/30 00:57:51 richard
# Now uses getopt, much improved command-line parsing. Much fuller help. Much
# better internal structure. It's just BETTER. :)
#
# Revision 1.5 2001/07/30 00:04:48 richard
# Made the "init" prompting more friendly.
#
# Revision 1.4 2001/07/29 07:01:39 richard
# Added vim command to all source so that we don't get no steenkin' tabs :)
#
# Revision 1.3 2001/07/23 08:45:28 richard
# ok, so now "./roundup-admin init" will ask questions in an attempt to get a
# workable instance_home set up :)
# _and_ anydbm has had its first test :)
#
# Revision 1.2 2001/07/23 08:20:44 richard
# Moved over to using marshal in the bsddb and anydbm backends.
# roundup-admin now has a "freshen" command that'll load/save all nodes (not
# retired - mod hyperdb.Class.list() so it lists retired nodes)
#
# Revision 1.1 2001/07/23 03:46:48 richard
# moving the bin files to facilitate out-of-the-boxness
#
# Revision 1.1 2001/07/22 11:15:45 richard
# More Grande Splite stuff
#
#
# vim: set filetype=python ts=4 sw=4 et si