Code

init help now lists templates and backends
[roundup.git] / roundup-admin
1 #! /usr/bin/python
2 # $Id: roundup-admin,v 1.9 2001-07-30 03:52:55 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
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.
177     '''
178     classname = args[0]
179     cl = db.getclass(classname)
181     # look up the linked-to class and get the nodeid that has the value
182     propname, value = args[1:].split('=')
183     propcl = cl[propname].classname
184     nodeid = propcl.lookup(value)
186     # now do the find
187     # TODO: handle the -c option
188     print cl.find(propname, nodeid)
189     return 0
191 def do_spec(db, args):
192     '''Usage: spec classname
193     Show the properties for a classname.
195     This lists the properties for a given class.
196     '''
197     classname = args[0]
198     cl = db.getclass(classname)
199     for key, value in cl.properties.items():
200         print '%s: %s'%(key, value)
202 def do_create(db, args):
203     '''Usage: create classname property=value ...
204     Create a new entry of a given class.
206     This creates a new entry of the given class using the property
207     name=value arguments provided on the command line after the "create"
208     command.
209     '''
210     classname = args[0]
211     cl = db.getclass(classname)
212     props = {}
213     properties = cl.getprops()
214     for prop in args[1:]:
215         key, value = prop.split('=')
216         type =  properties[key]
217         if type.isStringType:
218             props[key] = value 
219         elif type.isDateType:
220             props[key] = date.Date(value)
221         elif type.isIntervalType:
222             props[key] = date.Interval(value)
223         elif type.isLinkType:
224             props[key] = value
225         elif type.isMultilinkType:
226             props[key] = value.split(',')
227     print apply(cl.create, (), props)
228     return 0
230 def do_list(db, args):
231     '''Usage: list classname [property]
232     List the instances of a class.
234     Lists all instances of the given class along. If the property is not
235     specified, the  "label" property is used. The label property is tried
236     in order: the key, "name", "title" and then the first property,
237     alphabetically.
238     '''
239     db = instance.open()
240     classname = args[0]
241     cl = db.getclass(classname)
242     if len(args) > 1:
243         key = args[1]
244     else:
245         key = cl.labelprop()
246     # TODO: handle the -c option
247     for nodeid in cl.list():
248         value = cl.get(nodeid, key)
249         print "%4s: %s"%(nodeid, value)
250     return 0
252 def do_history(db, args):
253     '''Usage: history designator
254     Show the history entries of a designator.
256     Lists the journal entries for the node identified by the designator.
257     '''
258     classname, nodeid = roundupdb.splitDesignator(args[0])
259     # TODO: handle the -c option
260     print db.getclass(classname).history(nodeid)
261     return 0
263 def do_retire(db, args):
264     '''Usage: retire designator[,designator]*
265     Retire the node specified by designator.
267     This action indicates that a particular node is not to be retrieved by
268     the list or find commands, and its key value may be re-used.
269     '''
270     designators = string.split(args[0], ',')
271     for designator in designators:
272         classname, nodeid = roundupdb.splitDesignator(designator)
273         db.getclass(classname).retire(nodeid)
274     return 0
276 def do_freshen(db, args):
277     '''Usage: freshen
278     Freshen an existing instance.  **DO NOT USE**
280     This currently kills databases!!!!
282     This action should generally not be used. It reads in an instance
283     database and writes it again. In the future, is may also update
284     instance code to account for changes in templates. It's probably wise
285     not to use it anyway. Until we're sure it won't break things...
286     '''
287 #    for classname, cl in db.classes.items():
288 #        properties = cl.properties.items()
289 #        for nodeid in cl.list():
290 #            node = {}
291 #            for name, type in properties:
292 #                if type.isMultilinkType:
293 #                    node[name] = cl.get(nodeid, name, [])
294 #                else:
295 #                    node[name] = cl.get(nodeid, name, None)
296 #                db.setnode(classname, nodeid, node)
297     return 1
299 def figureCommands():
300     d = {}
301     for k, v in globals().items():
302         if k[:3] == 'do_':
303             d[k[3:]] = v
304     return d
306 def printInitOptions():
307     import roundup.templates
308     templates = roundup.templates.listTemplates()
309     print 'Templates:', ', '.join(templates)
310     import roundup.backends
311     backends = roundup.backends.__all__
312     print 'Back ends:', ', '.join(backends)
314 def main():
315     opts, args = getopt.getopt(sys.argv[1:], 'i:u:hc')
317     # handle command-line args
318     instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
319     name = password = ''
320     if os.environ.has_key('ROUNDUP_LOGIN'):
321         l = os.environ['ROUNDUP_LOGIN'].split(':')
322         name = l[0]
323         if len(l) > 1:
324             password = l[1]
325     comma_sep = 0
326     for opt, arg in opts:
327         if opt == '-h':
328             usage()
329             return 0
330         if opt == '-i':
331             instance_home = arg
332         if opt == '-u':
333             l = arg.split(':')
334             name = l[0]
335             if len(l) > 1:
336                 password = l[1]
337         if opt == '-c':
338             comma_sep = 1
340     # figure the command
341     if not args:
342         usage('No command specified')
343         return 1
344     command = args[0]
346     # handle help now
347     if command == 'help':
348         if len(args)>1:
349             command = figureCommands().get(args[1], None)
350             if not command:
351                 usage('no such command "%s"'%args[1])
352                 return 1
353             print command.__doc__
354             if args[1] == 'init':
355                 printInitOptions()
356             return 0
357         usage()
358         return 0
359     if command == 'morehelp':
360         moreusage()
361         return 0
363     # make sure we have an instance_home
364     while not instance_home:
365         instance_home = raw_input('Enter instance home: ').strip()
367     # before we open the db, we may be doing an init
368     if command == 'init':
369         return do_init(instance_home, args)
371     # open the database
372     if command in ('create', 'set', 'retire', 'freshen'):
373         while not name:
374             name = raw_input('Login name: ')
375         while not password:
376             password = getpass.getpass('  password: ')
378     # get the instance
379     path, instance = os.path.split(instance_home)
380     sys.path.insert(0, path)
381     try:
382         instance = __import__(instance)
383     finally:
384         del sys.path[0]
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.8  2001/07/30 02:37:07  richard
408 # Freshen is really broken. Commented out.
410 # Revision 1.7  2001/07/30 01:28:46  richard
411 # Bugfixes
413 # Revision 1.6  2001/07/30 00:57:51  richard
414 # Now uses getopt, much improved command-line parsing. Much fuller help. Much
415 # better internal structure. It's just BETTER. :)
417 # Revision 1.5  2001/07/30 00:04:48  richard
418 # Made the "init" prompting more friendly.
420 # Revision 1.4  2001/07/29 07:01:39  richard
421 # Added vim command to all source so that we don't get no steenkin' tabs :)
423 # Revision 1.3  2001/07/23 08:45:28  richard
424 # ok, so now "./roundup-admin init" will ask questions in an attempt to get a
425 # workable instance_home set up :)
426 # _and_ anydbm has had its first test :)
428 # Revision 1.2  2001/07/23 08:20:44  richard
429 # Moved over to using marshal in the bsddb and anydbm backends.
430 # roundup-admin now has a "freshen" command that'll load/save all nodes (not
431 #  retired - mod hyperdb.Class.list() so it lists retired nodes)
433 # Revision 1.1  2001/07/23 03:46:48  richard
434 # moving the bin files to facilitate out-of-the-boxness
436 # Revision 1.1  2001/07/22 11:15:45  richard
437 # More Grande Splite stuff
440 # vim: set filetype=python ts=4 sw=4 et si