Code

Instance import now imports the instance using imp.load_module so that
[roundup.git] / roundup-admin
1 #! /usr/bin/python
2 # $Id: roundup-admin,v 1.11 2001-08-03 00:59:34 richard Exp $
4 import sys
5 if int(sys.version[0]) < 2:
6     print 'Roundup requires python 2.0 or later.'
7     sys.exit(1)
9 import string, os, getpass, getopt, re, imp
10 from roundup import date, roundupdb, init
12 def usage(message=''):
13     if message: message = 'Problem: '+message+'\n'
14     commands = []
15     for command in figureCommands().values():
16         h = command.__doc__.split('\n')[0]
17         commands.append(h[7:])
18     commands.sort()
19     print '''%sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments>
21 Commands:
22  %s
24 Help:
25  roundup-admin -h
26  roundup-admin help    
27    -- this help
28  roundup-admin help <command>
29    -- command-specific help
30  roundup-admin morehelp
31    -- even more detailed help
32  
33 '''%(message, '\n '.join(commands))
35 def moreusage(message=''):
36     usage(message)
37     print '''
38 All commands (except help) require an instance specifier. This is just the path
39 to the roundup instance you're working with. It may be specified in the
40 environment variable ROUNDUP_INSTANCE or on the command line as "-i instance".
42 A designator is a classname and a nodeid concatenated, eg. bug1, user10, ...
44 Property values are represented as strings in command arguments and in the
45 printed results:
46  . Strings are, well, strings.
47  . Date values are printed in the full date format in the local time zone, and
48    accepted in the full format or any of the partial formats explained below.
49  . Link values are printed as node designators. When given as an argument,
50    node designators and key strings are both accepted.
51  . Multilink values are printed as lists of node designators joined by commas.
52    When given as an argument, node designators and key strings are both
53    accepted; an empty string, a single node, or a list of nodes joined by
54    commas is accepted.
56 When multiple nodes are specified to the roundup get or roundup set
57 commands, the specified properties are retrieved or set on all the listed
58 nodes. 
60 When multiple results are returned by the roundup get or roundup find
61 commands, they are printed one per line (default) or joined by commas (with
62 the -c) option. 
64 Where the command changes data, a login name/password is required. The
65 login may be specified as either "name" or "name:password".
66  . ROUNDUP_LOGIN environment variable
67  . the -u command-line option
68 If either the name or password is not supplied, they are obtained from the
69 command-line. 
71 Date format examples:
72   "2000-04-17.03:45" means <Date 2000-04-17.08:45:00>
73   "2000-04-17" means <Date 2000-04-17.00:00:00>
74   "01-25" means <Date yyyy-01-25.00:00:00>
75   "08-13.22:13" means <Date yyyy-08-14.03:13:00>
76   "11-07.09:32:43" means <Date yyyy-11-07.14:32:43>
77   "14:25" means <Date yyyy-mm-dd.19:25:00>
78   "8:47:11" means <Date yyyy-mm-dd.13:47:11>
79   "." means "right now"
81 Command help:
82 '''
83     for name, command in figureCommands().items():
84         print '%s:'%name
85         print '   ',command.__doc__
87 def do_init(instance_home, args):
88     '''Usage: init [template [backend [admin password]]]
89     Initialise a new Roundup instance.
91     The command will prompt for the instance home directory (if not supplied
92     through INSTANCE_HOME or the -i option. The template, backend and admin
93     password may be specified on the command-line as arguments, in that order.
94     '''
95     # select template
96     import roundup.templates
97     templates = roundup.templates.listTemplates()
98     template = len(args) > 1 and args[1] or ''
99     if template not in templates:
100         print 'Templates:', ', '.join(templates)
101     while template not in templates:
102         template = raw_input('Select template [classic]: ').strip()
103         if not template:
104             template = 'classic'
106     import roundup.backends
107     backends = roundup.backends.__all__
108     backend = len(args) > 2 and args[2] or ''
109     if backend not in backends:
110         print 'Back ends:', ', '.join(backends)
111     while backend not in backends:
112         backend = raw_input('Select backend [anydbm]: ').strip()
113         if not backend:
114             backend = 'anydbm'
115     if len(args) > 3:
116         adminpw = confirm = args[3]
117     else:
118         adminpw = ''
119         confirm = 'x'
120     while adminpw != confirm:
121         adminpw = getpass.getpass('Admin Password: ')
122         confirm = getpass.getpass('       Confirm: ')
123     init.init(instance_home, template, backend, adminpw)
124     return 0
127 def do_get(db, args):
128     '''Usage: get property designator[,designator]*
129     Get the given property of one or more designator(s).
131     Retrieves the property value of the nodes specified by the designators.
132     '''
133     designators = string.split(args[0], ',')
134     propname = args[1]
135     # TODO: handle the -c option
136     for designator in designators:
137         classname, nodeid = roundupdb.splitDesignator(designator)
138         print db.getclass(classname).get(nodeid, propname)
139     return 0
142 def do_set(db, args):
143     '''Usage: set designator[,designator]* propname=value ...
144     Set the given property of one or more designator(s).
146     Sets the property to the value for all designators given.
147     '''
148     designators = string.split(args[0], ',')
149     props = {}
150     for prop in args[1:]:
151         key, value = prop.split('=')
152         props[key] = value
153     for designator in designators:
154         classname, nodeid = roundupdb.splitDesignator(designator)
155         cl = db.getclass(classname)
156         properties = cl.getprops()
157         for key, value in props.items():
158             type =  properties[key]
159             if type.isStringType:
160                 continue
161             elif type.isDateType:
162                 props[key] = date.Date(value)
163             elif type.isIntervalType:
164                 props[key] = date.Interval(value)
165             elif type.isLinkType:
166                 props[key] = value
167             elif type.isMultilinkType:
168                 props[key] = value.split(',')
169         apply(cl.set, (nodeid, ), props)
170     return 0
172 def do_find(db, args):
173     '''Usage: find classname propname=value ...
174     Find the nodes of the given class with a given property value.
176     Find the nodes of the given class with a given property value. The
177     value may be either the nodeid of the linked node, or its key value.
178     '''
179     classname = args[0]
180     cl = db.getclass(classname)
182     # look up the linked-to class and get the nodeid that has the value
183     propname, value = args[1].split('=')
184     num_re = re.compile('^\d+$')
185     if num_re.match(value):
186         nodeid = value
187     else:
188         propcl = cl.properties[propname].classname
189         propcl = db.getclass(propcl)
190         nodeid = propcl.lookup(value)
192     # now do the find
193     # TODO: handle the -c option
194     print cl.find(**{propname: nodeid})
195     return 0
197 def do_spec(db, args):
198     '''Usage: spec classname
199     Show the properties for a classname.
201     This lists the properties for a given class.
202     '''
203     classname = args[0]
204     cl = db.getclass(classname)
205     for key, value in cl.properties.items():
206         print '%s: %s'%(key, value)
208 def do_create(db, args):
209     '''Usage: create classname property=value ...
210     Create a new entry of a given class.
212     This creates a new entry of the given class using the property
213     name=value arguments provided on the command line after the "create"
214     command.
215     '''
216     classname = args[0]
217     cl = db.getclass(classname)
218     props = {}
219     properties = cl.getprops()
220     for prop in args[1:]:
221         key, value = prop.split('=')
222         type =  properties[key]
223         if type.isStringType:
224             props[key] = value 
225         elif type.isDateType:
226             props[key] = date.Date(value)
227         elif type.isIntervalType:
228             props[key] = date.Interval(value)
229         elif type.isLinkType:
230             props[key] = value
231         elif type.isMultilinkType:
232             props[key] = value.split(',')
233     print apply(cl.create, (), props)
234     return 0
236 def do_list(db, args):
237     '''Usage: list classname [property]
238     List the instances of a class.
240     Lists all instances of the given class along. If the property is not
241     specified, the  "label" property is used. The label property is tried
242     in order: the key, "name", "title" and then the first property,
243     alphabetically.
244     '''
245     classname = args[0]
246     cl = db.getclass(classname)
247     if len(args) > 1:
248         key = args[1]
249     else:
250         key = cl.labelprop()
251     # TODO: handle the -c option
252     for nodeid in cl.list():
253         value = cl.get(nodeid, key)
254         print "%4s: %s"%(nodeid, value)
255     return 0
257 def do_history(db, args):
258     '''Usage: history designator
259     Show the history entries of a designator.
261     Lists the journal entries for the node identified by the designator.
262     '''
263     classname, nodeid = roundupdb.splitDesignator(args[0])
264     # TODO: handle the -c option
265     print db.getclass(classname).history(nodeid)
266     return 0
268 def do_retire(db, args):
269     '''Usage: retire designator[,designator]*
270     Retire the node specified by designator.
272     This action indicates that a particular node is not to be retrieved by
273     the list or find commands, and its key value may be re-used.
274     '''
275     designators = string.split(args[0], ',')
276     for designator in designators:
277         classname, nodeid = roundupdb.splitDesignator(designator)
278         db.getclass(classname).retire(nodeid)
279     return 0
281 def do_freshen(db, args):
282     '''Usage: freshen
283     Freshen an existing instance.  **DO NOT USE**
285     This currently kills databases!!!!
287     This action should generally not be used. It reads in an instance
288     database and writes it again. In the future, is may also update
289     instance code to account for changes in templates. It's probably wise
290     not to use it anyway. Until we're sure it won't break things...
291     '''
292 #    for classname, cl in db.classes.items():
293 #        properties = cl.properties.items()
294 #        for nodeid in cl.list():
295 #            node = {}
296 #            for name, type in properties:
297 #                if type.isMultilinkType:
298 #                    node[name] = cl.get(nodeid, name, [])
299 #                else:
300 #                    node[name] = cl.get(nodeid, name, None)
301 #                db.setnode(classname, nodeid, node)
302     return 1
304 def figureCommands():
305     d = {}
306     for k, v in globals().items():
307         if k[:3] == 'do_':
308             d[k[3:]] = v
309     return d
311 def printInitOptions():
312     import roundup.templates
313     templates = roundup.templates.listTemplates()
314     print 'Templates:', ', '.join(templates)
315     import roundup.backends
316     backends = roundup.backends.__all__
317     print 'Back ends:', ', '.join(backends)
319 def main():
320     opts, args = getopt.getopt(sys.argv[1:], 'i:u:hc')
322     # handle command-line args
323     instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
324     name = password = ''
325     if os.environ.has_key('ROUNDUP_LOGIN'):
326         l = os.environ['ROUNDUP_LOGIN'].split(':')
327         name = l[0]
328         if len(l) > 1:
329             password = l[1]
330     comma_sep = 0
331     for opt, arg in opts:
332         if opt == '-h':
333             usage()
334             return 0
335         if opt == '-i':
336             instance_home = arg
337         if opt == '-u':
338             l = arg.split(':')
339             name = l[0]
340             if len(l) > 1:
341                 password = l[1]
342         if opt == '-c':
343             comma_sep = 1
345     # figure the command
346     if not args:
347         usage('No command specified')
348         return 1
349     command = args[0]
351     # handle help now
352     if command == 'help':
353         if len(args)>1:
354             command = figureCommands().get(args[1], None)
355             if not command:
356                 usage('no such command "%s"'%args[1])
357                 return 1
358             print command.__doc__
359             if args[1] == 'init':
360                 printInitOptions()
361             return 0
362         usage()
363         return 0
364     if command == 'morehelp':
365         moreusage()
366         return 0
368     # make sure we have an instance_home
369     while not instance_home:
370         instance_home = raw_input('Enter instance home: ').strip()
372     # before we open the db, we may be doing an init
373     if command == 'init':
374         return do_init(instance_home, args)
376     # open the database
377     if command in ('create', 'set', 'retire', 'freshen'):
378         while not name:
379             name = raw_input('Login name: ')
380         while not password:
381             password = getpass.getpass('  password: ')
383     # get the instance
384     instance = imp.load_module('instance', None, instance_home, ('', '', 5))
386     function = figureCommands().get(command, None)
388     # not a valid command
389     if function is None:
390         usage('Unknown command "%s"'%command)
391         return 1
393     db = instance.open(name or 'admin')
394     try:
395         return function(db, args[1:])
396     finally:
397         db.close()
399     return 1
402 if __name__ == '__main__':
403     sys.exit(main())
406 # $Log: not supported by cvs2svn $
407 # Revision 1.10  2001/07/30 08:12:17  richard
408 # Added time logging and file uploading to the templates.
410 # Revision 1.9  2001/07/30 03:52:55  richard
411 # init help now lists templates and backends
413 # Revision 1.8  2001/07/30 02:37:07  richard
414 # Freshen is really broken. Commented out.
416 # Revision 1.7  2001/07/30 01:28:46  richard
417 # Bugfixes
419 # Revision 1.6  2001/07/30 00:57:51  richard
420 # Now uses getopt, much improved command-line parsing. Much fuller help. Much
421 # better internal structure. It's just BETTER. :)
423 # Revision 1.5  2001/07/30 00:04:48  richard
424 # Made the "init" prompting more friendly.
426 # Revision 1.4  2001/07/29 07:01:39  richard
427 # Added vim command to all source so that we don't get no steenkin' tabs :)
429 # Revision 1.3  2001/07/23 08:45:28  richard
430 # ok, so now "./roundup-admin init" will ask questions in an attempt to get a
431 # workable instance_home set up :)
432 # _and_ anydbm has had its first test :)
434 # Revision 1.2  2001/07/23 08:20:44  richard
435 # Moved over to using marshal in the bsddb and anydbm backends.
436 # roundup-admin now has a "freshen" command that'll load/save all nodes (not
437 #  retired - mod hyperdb.Class.list() so it lists retired nodes)
439 # Revision 1.1  2001/07/23 03:46:48  richard
440 # moving the bin files to facilitate out-of-the-boxness
442 # Revision 1.1  2001/07/22 11:15:45  richard
443 # More Grande Splite stuff
446 # vim: set filetype=python ts=4 sw=4 et si