Code

0e89b2aa39b7880d19169b8ce755b0d48e81480e
[roundup.git] / roundup-admin
1 #! /usr/bin/python
2 #
3 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
4 # This module is free software, and you may redistribute it and/or modify
5 # under the same terms as Python, so long as this copyright message and
6 # disclaimer are retained in their original form.
7 #
8 # IN NO EVENT SHALL THE BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
9 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
10 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
11 # POSSIBILITY OF SUCH DAMAGE.
12 #
13 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
14 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
15 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
16 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
18
19 # $Id: roundup-admin,v 1.14 2001-08-07 00:15:51 richard Exp $
21 import sys
22 if int(sys.version[0]) < 2:
23     print 'Roundup requires python 2.0 or later.'
24     sys.exit(1)
26 import string, os, getpass, getopt, re
27 from roundup import date, roundupdb, init
28 import roundup.instance
30 def usage(message=''):
31     if message: message = 'Problem: '+message+'\n'
32     commands = []
33     for command in figureCommands().values():
34         h = command.__doc__.split('\n')[0]
35         commands.append(h[7:])
36     commands.sort()
37     print '''%sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments>
39 Commands:
40  %s
42 Help:
43  roundup-admin -h
44  roundup-admin help    
45    -- this help
46  roundup-admin help <command>
47    -- command-specific help
48  roundup-admin morehelp
49    -- even more detailed help
50  
51 '''%(message, '\n '.join(commands))
53 def moreusage(message=''):
54     usage(message)
55     print '''
56 All commands (except help) require an instance specifier. This is just the path
57 to the roundup instance you're working with. It may be specified in the
58 environment variable ROUNDUP_INSTANCE or on the command line as "-i instance".
60 A designator is a classname and a nodeid concatenated, eg. bug1, user10, ...
62 Property values are represented as strings in command arguments and in the
63 printed results:
64  . Strings are, well, strings.
65  . Date values are printed in the full date format in the local time zone, and
66    accepted in the full format or any of the partial formats explained below.
67  . Link values are printed as node designators. When given as an argument,
68    node designators and key strings are both accepted.
69  . Multilink values are printed as lists of node designators joined by commas.
70    When given as an argument, node designators and key strings are both
71    accepted; an empty string, a single node, or a list of nodes joined by
72    commas is accepted.
74 When multiple nodes are specified to the roundup get or roundup set
75 commands, the specified properties are retrieved or set on all the listed
76 nodes. 
78 When multiple results are returned by the roundup get or roundup find
79 commands, they are printed one per line (default) or joined by commas (with
80 the -c) option. 
82 Where the command changes data, a login name/password is required. The
83 login may be specified as either "name" or "name:password".
84  . ROUNDUP_LOGIN environment variable
85  . the -u command-line option
86 If either the name or password is not supplied, they are obtained from the
87 command-line. 
89 Date format examples:
90   "2000-04-17.03:45" means <Date 2000-04-17.08:45:00>
91   "2000-04-17" means <Date 2000-04-17.00:00:00>
92   "01-25" means <Date yyyy-01-25.00:00:00>
93   "08-13.22:13" means <Date yyyy-08-14.03:13:00>
94   "11-07.09:32:43" means <Date yyyy-11-07.14:32:43>
95   "14:25" means <Date yyyy-mm-dd.19:25:00>
96   "8:47:11" means <Date yyyy-mm-dd.13:47:11>
97   "." means "right now"
99 Command help:
100 '''
101     for name, command in figureCommands().items():
102         print '%s:'%name
103         print '   ',command.__doc__
105 def do_init(instance_home, args):
106     '''Usage: init [template [backend [admin password]]]
107     Initialise a new Roundup instance.
109     The command will prompt for the instance home directory (if not supplied
110     through INSTANCE_HOME or the -i option. The template, backend and admin
111     password may be specified on the command-line as arguments, in that order.
112     '''
113     # select template
114     import roundup.templates
115     templates = roundup.templates.listTemplates()
116     template = len(args) > 1 and args[1] or ''
117     if template not in templates:
118         print 'Templates:', ', '.join(templates)
119     while template not in templates:
120         template = raw_input('Select template [classic]: ').strip()
121         if not template:
122             template = 'classic'
124     import roundup.backends
125     backends = roundup.backends.__all__
126     backend = len(args) > 2 and args[2] or ''
127     if backend not in backends:
128         print 'Back ends:', ', '.join(backends)
129     while backend not in backends:
130         backend = raw_input('Select backend [anydbm]: ').strip()
131         if not backend:
132             backend = 'anydbm'
133     if len(args) > 3:
134         adminpw = confirm = args[3]
135     else:
136         adminpw = ''
137         confirm = 'x'
138     while adminpw != confirm:
139         adminpw = getpass.getpass('Admin Password: ')
140         confirm = getpass.getpass('       Confirm: ')
141     init.init(instance_home, template, backend, adminpw)
142     return 0
145 def do_get(db, args):
146     '''Usage: get property designator[,designator]*
147     Get the given property of one or more designator(s).
149     Retrieves the property value of the nodes specified by the designators.
150     '''
151     designators = string.split(args[0], ',')
152     propname = args[1]
153     # TODO: handle the -c option
154     for designator in designators:
155         classname, nodeid = roundupdb.splitDesignator(designator)
156         print db.getclass(classname).get(nodeid, propname)
157     return 0
160 def do_set(db, args):
161     '''Usage: set designator[,designator]* propname=value ...
162     Set the given property of one or more designator(s).
164     Sets the property to the value for all designators given.
165     '''
166     designators = string.split(args[0], ',')
167     props = {}
168     for prop in args[1:]:
169         key, value = prop.split('=')
170         props[key] = value
171     for designator in designators:
172         classname, nodeid = roundupdb.splitDesignator(designator)
173         cl = db.getclass(classname)
174         properties = cl.getprops()
175         for key, value in props.items():
176             type =  properties[key]
177             if type.isStringType:
178                 continue
179             elif type.isDateType:
180                 props[key] = date.Date(value)
181             elif type.isIntervalType:
182                 props[key] = date.Interval(value)
183             elif type.isLinkType:
184                 props[key] = value
185             elif type.isMultilinkType:
186                 props[key] = value.split(',')
187         apply(cl.set, (nodeid, ), props)
188     return 0
190 def do_find(db, args):
191     '''Usage: find classname propname=value ...
192     Find the nodes of the given class with a given property value.
194     Find the nodes of the given class with a given property value. The
195     value may be either the nodeid of the linked node, or its key value.
196     '''
197     classname = args[0]
198     cl = db.getclass(classname)
200     # look up the linked-to class and get the nodeid that has the value
201     propname, value = args[1].split('=')
202     num_re = re.compile('^\d+$')
203     if num_re.match(value):
204         nodeid = value
205     else:
206         propcl = cl.properties[propname].classname
207         propcl = db.getclass(propcl)
208         nodeid = propcl.lookup(value)
210     # now do the find
211     # TODO: handle the -c option
212     print cl.find(**{propname: nodeid})
213     return 0
215 def do_spec(db, args):
216     '''Usage: spec classname
217     Show the properties for a classname.
219     This lists the properties for a given class.
220     '''
221     classname = args[0]
222     cl = db.getclass(classname)
223     for key, value in cl.properties.items():
224         print '%s: %s'%(key, value)
226 def do_create(db, args):
227     '''Usage: create classname property=value ...
228     Create a new entry of a given class.
230     This creates a new entry of the given class using the property
231     name=value arguments provided on the command line after the "create"
232     command.
233     '''
234     classname = args[0]
235     cl = db.getclass(classname)
236     props = {}
237     properties = cl.getprops()
238     for prop in args[1:]:
239         key, value = prop.split('=')
240         type =  properties[key]
241         if type.isStringType:
242             props[key] = value 
243         elif type.isDateType:
244             props[key] = date.Date(value)
245         elif type.isIntervalType:
246             props[key] = date.Interval(value)
247         elif type.isLinkType:
248             props[key] = value
249         elif type.isMultilinkType:
250             props[key] = value.split(',')
251     print apply(cl.create, (), props)
252     return 0
254 def do_list(db, args):
255     '''Usage: list classname [property]
256     List the instances of a class.
258     Lists all instances of the given class along. If the property is not
259     specified, the  "label" property is used. The label property is tried
260     in order: the key, "name", "title" and then the first property,
261     alphabetically.
262     '''
263     classname = args[0]
264     cl = db.getclass(classname)
265     if len(args) > 1:
266         key = args[1]
267     else:
268         key = cl.labelprop()
269     # TODO: handle the -c option
270     for nodeid in cl.list():
271         value = cl.get(nodeid, key)
272         print "%4s: %s"%(nodeid, value)
273     return 0
275 def do_history(db, args):
276     '''Usage: history designator
277     Show the history entries of a designator.
279     Lists the journal entries for the node identified by the designator.
280     '''
281     classname, nodeid = roundupdb.splitDesignator(args[0])
282     # TODO: handle the -c option
283     print db.getclass(classname).history(nodeid)
284     return 0
286 def do_retire(db, args):
287     '''Usage: retire designator[,designator]*
288     Retire the node specified by designator.
290     This action indicates that a particular node is not to be retrieved by
291     the list or find commands, and its key value may be re-used.
292     '''
293     designators = string.split(args[0], ',')
294     for designator in designators:
295         classname, nodeid = roundupdb.splitDesignator(designator)
296         db.getclass(classname).retire(nodeid)
297     return 0
299 def do_freshen(db, args):
300     '''Usage: freshen
301     Freshen an existing instance.  **DO NOT USE**
303     This currently kills databases!!!!
305     This action should generally not be used. It reads in an instance
306     database and writes it again. In the future, is may also update
307     instance code to account for changes in templates. It's probably wise
308     not to use it anyway. Until we're sure it won't break things...
309     '''
310 #    for classname, cl in db.classes.items():
311 #        properties = cl.properties.items()
312 #        for nodeid in cl.list():
313 #            node = {}
314 #            for name, type in properties:
315 #                if type.isMultilinkType:
316 #                    node[name] = cl.get(nodeid, name, [])
317 #                else:
318 #                    node[name] = cl.get(nodeid, name, None)
319 #                db.setnode(classname, nodeid, node)
320     return 1
322 def figureCommands():
323     d = {}
324     for k, v in globals().items():
325         if k[:3] == 'do_':
326             d[k[3:]] = v
327     return d
329 def printInitOptions():
330     import roundup.templates
331     templates = roundup.templates.listTemplates()
332     print 'Templates:', ', '.join(templates)
333     import roundup.backends
334     backends = roundup.backends.__all__
335     print 'Back ends:', ', '.join(backends)
337 def main():
338     opts, args = getopt.getopt(sys.argv[1:], 'i:u:hc')
340     # handle command-line args
341     instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
342     name = password = ''
343     if os.environ.has_key('ROUNDUP_LOGIN'):
344         l = os.environ['ROUNDUP_LOGIN'].split(':')
345         name = l[0]
346         if len(l) > 1:
347             password = l[1]
348     comma_sep = 0
349     for opt, arg in opts:
350         if opt == '-h':
351             usage()
352             return 0
353         if opt == '-i':
354             instance_home = arg
355         if opt == '-u':
356             l = arg.split(':')
357             name = l[0]
358             if len(l) > 1:
359                 password = l[1]
360         if opt == '-c':
361             comma_sep = 1
363     # figure the command
364     if not args:
365         usage('No command specified')
366         return 1
367     command = args[0]
369     # handle help now
370     if command == 'help':
371         if len(args)>1:
372             command = figureCommands().get(args[1], None)
373             if not command:
374                 usage('no such command "%s"'%args[1])
375                 return 1
376             print command.__doc__
377             if args[1] == 'init':
378                 printInitOptions()
379             return 0
380         usage()
381         return 0
382     if command == 'morehelp':
383         moreusage()
384         return 0
386     # make sure we have an instance_home
387     while not instance_home:
388         instance_home = raw_input('Enter instance home: ').strip()
390     # before we open the db, we may be doing an init
391     if command == 'init':
392         return do_init(instance_home, args)
394     # open the database
395     if command in ('create', 'set', 'retire', 'freshen'):
396         while not name:
397             name = raw_input('Login name: ')
398         while not password:
399             password = getpass.getpass('  password: ')
401     # get the instance
402     instance = roundup.instance.open(instance_home)
404     function = figureCommands().get(command, None)
406     # not a valid command
407     if function is None:
408         usage('Unknown command "%s"'%command)
409         return 1
411     db = instance.open(name or 'admin')
412     try:
413         return function(db, args[1:])
414     finally:
415         db.close()
417     return 1
420 if __name__ == '__main__':
421     sys.exit(main())
424 # $Log: not supported by cvs2svn $
425 # Revision 1.13  2001/08/05 07:44:13  richard
426 # Instances are now opened by a special function that generates a unique
427 # module name for the instances on import time.
429 # Revision 1.12  2001/08/03 01:28:33  richard
430 # Used the much nicer load_package, pointed out by Steve Majewski.
432 # Revision 1.11  2001/08/03 00:59:34  richard
433 # Instance import now imports the instance using imp.load_module so that
434 # we can have instance homes of "roundup" or other existing python package
435 # names.
437 # Revision 1.10  2001/07/30 08:12:17  richard
438 # Added time logging and file uploading to the templates.
440 # Revision 1.9  2001/07/30 03:52:55  richard
441 # init help now lists templates and backends
443 # Revision 1.8  2001/07/30 02:37:07  richard
444 # Freshen is really broken. Commented out.
446 # Revision 1.7  2001/07/30 01:28:46  richard
447 # Bugfixes
449 # Revision 1.6  2001/07/30 00:57:51  richard
450 # Now uses getopt, much improved command-line parsing. Much fuller help. Much
451 # better internal structure. It's just BETTER. :)
453 # Revision 1.5  2001/07/30 00:04:48  richard
454 # Made the "init" prompting more friendly.
456 # Revision 1.4  2001/07/29 07:01:39  richard
457 # Added vim command to all source so that we don't get no steenkin' tabs :)
459 # Revision 1.3  2001/07/23 08:45:28  richard
460 # ok, so now "./roundup-admin init" will ask questions in an attempt to get a
461 # workable instance_home set up :)
462 # _and_ anydbm has had its first test :)
464 # Revision 1.2  2001/07/23 08:20:44  richard
465 # Moved over to using marshal in the bsddb and anydbm backends.
466 # roundup-admin now has a "freshen" command that'll load/save all nodes (not
467 #  retired - mod hyperdb.Class.list() so it lists retired nodes)
469 # Revision 1.1  2001/07/23 03:46:48  richard
470 # moving the bin files to facilitate out-of-the-boxness
472 # Revision 1.1  2001/07/22 11:15:45  richard
473 # More Grande Splite stuff
476 # vim: set filetype=python ts=4 sw=4 et si