Code

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