#! /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.17 2001-08-28 05:58:33 anthonybaxter 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] Commands: %s Help: roundup-admin -h roundup-admin help -- this help roundup-admin help -- 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 "2000-04-17" means "01-25" means "08-13.22:13" means "11-07.09:32:43" means "14:25" means "8:47:11" means "." 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. ''' 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(): type = properties[key] if isinstance(type, hyperdb.String): continue 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(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. ''' from roundup import hyperdb classname = args[0] cl = db.getclass(classname) props = {} properties = cl.getprops() for prop in args[1:]: key, value = prop.split('=') type = properties[key] if isinstance(type, hyperdb.String): props[key] = 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(',') 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: # 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(): 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.16 2001/08/12 06:32:36 richard # using isinstance(blah, Foo) now instead of isFooType # # Revision 1.15 2001/08/07 00:24:42 richard # stupid typo # # 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