Code

Disabled the bsddb3 module entirely in the unit testing. See CHANGES for
[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 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.17 2001-08-28 05:58:33 anthonybaxter 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     from roundup import hyperdb
168     designators = string.split(args[0], ',')
169     props = {}
170     for prop in args[1:]:
171         key, value = prop.split('=')
172         props[key] = value
173     for designator in designators:
174         classname, nodeid = roundupdb.splitDesignator(designator)
175         cl = db.getclass(classname)
176         properties = cl.getprops()
177         for key, value in props.items():
178             type =  properties[key]
179             if isinstance(type, hyperdb.String):
180                 continue
181             elif isinstance(type, hyperdb.Date):
182                 props[key] = date.Date(value)
183             elif isinstance(type, hyperdb.Interval):
184                 props[key] = date.Interval(value)
185             elif isinstance(type, hyperdb.Link):
186                 props[key] = value
187             elif isinstance(type, hyperdb.Multilink):
188                 props[key] = value.split(',')
189         apply(cl.set, (nodeid, ), props)
190     return 0
192 def do_find(db, args):
193     '''Usage: find classname propname=value ...
194     Find the nodes of the given class with a given property value.
196     Find the nodes of the given class with a given property value. The
197     value may be either the nodeid of the linked node, or its key value.
198     '''
199     classname = args[0]
200     cl = db.getclass(classname)
202     # look up the linked-to class and get the nodeid that has the value
203     propname, value = args[1].split('=')
204     num_re = re.compile('^\d+$')
205     if num_re.match(value):
206         nodeid = value
207     else:
208         propcl = cl.properties[propname].classname
209         propcl = db.getclass(propcl)
210         nodeid = propcl.lookup(value)
212     # now do the find
213     # TODO: handle the -c option
214     print cl.find(**{propname: nodeid})
215     return 0
217 def do_spec(db, args):
218     '''Usage: spec classname
219     Show the properties for a classname.
221     This lists the properties for a given class.
222     '''
223     classname = args[0]
224     cl = db.getclass(classname)
225     for key, value in cl.properties.items():
226         print '%s: %s'%(key, value)
228 def do_create(db, args):
229     '''Usage: create classname property=value ...
230     Create a new entry of a given class.
232     This creates a new entry of the given class using the property
233     name=value arguments provided on the command line after the "create"
234     command.
235     '''
236     from roundup import hyperdb
238     classname = args[0]
239     cl = db.getclass(classname)
240     props = {}
241     properties = cl.getprops()
242     for prop in args[1:]:
243         key, value = prop.split('=')
244         type =  properties[key]
245         if isinstance(type, hyperdb.String):
246             props[key] = value 
247         elif isinstance(type, hyperdb.Date):
248             props[key] = date.Date(value)
249         elif isinstance(type, hyperdb.Interval):
250             props[key] = date.Interval(value)
251         elif isinstance(type, hyperdb.Link):
252             props[key] = value
253         elif isinstance(type, hyperdb.Multilink):
254             props[key] = value.split(',')
255     print apply(cl.create, (), props)
256     return 0
258 def do_list(db, args):
259     '''Usage: list classname [property]
260     List the instances of a class.
262     Lists all instances of the given class along. If the property is not
263     specified, the  "label" property is used. The label property is tried
264     in order: the key, "name", "title" and then the first property,
265     alphabetically.
266     '''
267     classname = args[0]
268     cl = db.getclass(classname)
269     if len(args) > 1:
270         key = args[1]
271     else:
272         key = cl.labelprop()
273     # TODO: handle the -c option
274     for nodeid in cl.list():
275         value = cl.get(nodeid, key)
276         print "%4s: %s"%(nodeid, value)
277     return 0
279 def do_history(db, args):
280     '''Usage: history designator
281     Show the history entries of a designator.
283     Lists the journal entries for the node identified by the designator.
284     '''
285     classname, nodeid = roundupdb.splitDesignator(args[0])
286     # TODO: handle the -c option
287     print db.getclass(classname).history(nodeid)
288     return 0
290 def do_retire(db, args):
291     '''Usage: retire designator[,designator]*
292     Retire the node specified by designator.
294     This action indicates that a particular node is not to be retrieved by
295     the list or find commands, and its key value may be re-used.
296     '''
297     designators = string.split(args[0], ',')
298     for designator in designators:
299         classname, nodeid = roundupdb.splitDesignator(designator)
300         db.getclass(classname).retire(nodeid)
301     return 0
303 def do_freshen(db, args):
304     '''Usage: freshen
305     Freshen an existing instance.  **DO NOT USE**
307     This currently kills databases!!!!
309     This action should generally not be used. It reads in an instance
310     database and writes it again. In the future, is may also update
311     instance code to account for changes in templates. It's probably wise
312     not to use it anyway. Until we're sure it won't break things...
313     '''
314 #    for classname, cl in db.classes.items():
315 #        properties = cl.properties.items()
316 #        for nodeid in cl.list():
317 #            node = {}
318 #            for name, type in properties:
319 # isinstance(               if type, hyperdb.Multilink):
320 #                    node[name] = cl.get(nodeid, name, [])
321 #                else:
322 #                    node[name] = cl.get(nodeid, name, None)
323 #                db.setnode(classname, nodeid, node)
324     return 1
326 def figureCommands():
327     d = {}
328     for k, v in globals().items():
329         if k[:3] == 'do_':
330             d[k[3:]] = v
331     return d
333 def printInitOptions():
334     import roundup.templates
335     templates = roundup.templates.listTemplates()
336     print 'Templates:', ', '.join(templates)
337     import roundup.backends
338     backends = roundup.backends.__all__
339     print 'Back ends:', ', '.join(backends)
341 def main():
342     opts, args = getopt.getopt(sys.argv[1:], 'i:u:hc')
344     # handle command-line args
345     instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
346     name = password = ''
347     if os.environ.has_key('ROUNDUP_LOGIN'):
348         l = os.environ['ROUNDUP_LOGIN'].split(':')
349         name = l[0]
350         if len(l) > 1:
351             password = l[1]
352     comma_sep = 0
353     for opt, arg in opts:
354         if opt == '-h':
355             usage()
356             return 0
357         if opt == '-i':
358             instance_home = arg
359         if opt == '-u':
360             l = arg.split(':')
361             name = l[0]
362             if len(l) > 1:
363                 password = l[1]
364         if opt == '-c':
365             comma_sep = 1
367     # figure the command
368     if not args:
369         usage('No command specified')
370         return 1
371     command = args[0]
373     # handle help now
374     if command == 'help':
375         if len(args)>1:
376             command = figureCommands().get(args[1], None)
377             if not command:
378                 usage('no such command "%s"'%args[1])
379                 return 1
380             print command.__doc__
381             if args[1] == 'init':
382                 printInitOptions()
383             return 0
384         usage()
385         return 0
386     if command == 'morehelp':
387         moreusage()
388         return 0
390     # make sure we have an instance_home
391     while not instance_home:
392         instance_home = raw_input('Enter instance home: ').strip()
394     # before we open the db, we may be doing an init
395     if command == 'init':
396         return do_init(instance_home, args)
398     # open the database
399     if command in ('create', 'set', 'retire', 'freshen'):
400         while not name:
401             name = raw_input('Login name: ')
402         while not password:
403             password = getpass.getpass('  password: ')
405     # get the instance
406     instance = roundup.instance.open(instance_home)
408     function = figureCommands().get(command, None)
410     # not a valid command
411     if function is None:
412         usage('Unknown command "%s"'%command)
413         return 1
415     db = instance.open(name or 'admin')
416     try:
417         return function(db, args[1:])
418     finally:
419         db.close()
421     return 1
424 if __name__ == '__main__':
425     sys.exit(main())
428 # $Log: not supported by cvs2svn $
429 # Revision 1.16  2001/08/12 06:32:36  richard
430 # using isinstance(blah, Foo) now instead of isFooType
432 # Revision 1.15  2001/08/07 00:24:42  richard
433 # stupid typo
435 # Revision 1.14  2001/08/07 00:15:51  richard
436 # Added the copyright/license notice to (nearly) all files at request of
437 # Bizar Software.
439 # Revision 1.13  2001/08/05 07:44:13  richard
440 # Instances are now opened by a special function that generates a unique
441 # module name for the instances on import time.
443 # Revision 1.12  2001/08/03 01:28:33  richard
444 # Used the much nicer load_package, pointed out by Steve Majewski.
446 # Revision 1.11  2001/08/03 00:59:34  richard
447 # Instance import now imports the instance using imp.load_module so that
448 # we can have instance homes of "roundup" or other existing python package
449 # names.
451 # Revision 1.10  2001/07/30 08:12:17  richard
452 # Added time logging and file uploading to the templates.
454 # Revision 1.9  2001/07/30 03:52:55  richard
455 # init help now lists templates and backends
457 # Revision 1.8  2001/07/30 02:37:07  richard
458 # Freshen is really broken. Commented out.
460 # Revision 1.7  2001/07/30 01:28:46  richard
461 # Bugfixes
463 # Revision 1.6  2001/07/30 00:57:51  richard
464 # Now uses getopt, much improved command-line parsing. Much fuller help. Much
465 # better internal structure. It's just BETTER. :)
467 # Revision 1.5  2001/07/30 00:04:48  richard
468 # Made the "init" prompting more friendly.
470 # Revision 1.4  2001/07/29 07:01:39  richard
471 # Added vim command to all source so that we don't get no steenkin' tabs :)
473 # Revision 1.3  2001/07/23 08:45:28  richard
474 # ok, so now "./roundup-admin init" will ask questions in an attempt to get a
475 # workable instance_home set up :)
476 # _and_ anydbm has had its first test :)
478 # Revision 1.2  2001/07/23 08:20:44  richard
479 # Moved over to using marshal in the bsddb and anydbm backends.
480 # roundup-admin now has a "freshen" command that'll load/save all nodes (not
481 #  retired - mod hyperdb.Class.list() so it lists retired nodes)
483 # Revision 1.1  2001/07/23 03:46:48  richard
484 # moving the bin files to facilitate out-of-the-boxness
486 # Revision 1.1  2001/07/22 11:15:45  richard
487 # More Grande Splite stuff
490 # vim: set filetype=python ts=4 sw=4 et si