Code

ccde1a8639de96ce9efa2fc62756e9164d59396b
[roundup.git] / roundup-admin
1 #! /usr/bin/python
2 # $Id: roundup-admin,v 1.8 2001-07-30 02:37:07 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 main():
307     opts, args = getopt.getopt(sys.argv[1:], 'i:u:hc')
309     # handle command-line args
310     instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
311     name = password = ''
312     if os.environ.has_key('ROUNDUP_LOGIN'):
313         l = os.environ['ROUNDUP_LOGIN'].split(':')
314         name = l[0]
315         if len(l) > 1:
316             password = l[1]
317     comma_sep = 0
318     for opt, arg in opts:
319         if opt == '-h':
320             usage()
321             return 0
322         if opt == '-i':
323             instance_home = arg
324         if opt == '-u':
325             l = arg.split(':')
326             name = l[0]
327             if len(l) > 1:
328                 password = l[1]
329         if opt == '-c':
330             comma_sep = 1
332     # figure the command
333     if not args:
334         usage('No command specified')
335         return 1
336     command = args[0]
338     # handle help now
339     if command == 'help':
340         if len(args)>1:
341             command = figureCommands().get(args[1], None)
342             if not command:
343                 usage('no such command "%s"'%args[1])
344                 return 1
345             print command.__doc__
346             return 0
347         usage()
348         return 0
349     if command == 'morehelp':
350         moreusage()
351         return 0
353     # make sure we have an instance_home
354     while not instance_home:
355         instance_home = raw_input('Enter instance home: ').strip()
357     # before we open the db, we may be doing an init
358     if command == 'init':
359         return do_init(instance_home, args)
361     # open the database
362     if command in ('create', 'set', 'retire', 'freshen'):
363         while not name:
364             name = raw_input('Login name: ')
365         while not password:
366             password = getpass.getpass('  password: ')
368     # get the instance
369     path, instance = os.path.split(instance_home)
370     sys.path.insert(0, path)
371     try:
372         instance = __import__(instance)
373     finally:
374         del sys.path[0]
376     function = figureCommands().get(command, None)
378     # not a valid command
379     if function is None:
380         usage('Unknown command "%s"'%command)
381         return 1
383     db = instance.open(name or 'admin')
384     try:
385         return function(db, args[1:])
386     finally:
387         db.close()
389     return 1
392 if __name__ == '__main__':
393     sys.exit(main())
396 # $Log: not supported by cvs2svn $
397 # Revision 1.7  2001/07/30 01:28:46  richard
398 # Bugfixes
400 # Revision 1.6  2001/07/30 00:57:51  richard
401 # Now uses getopt, much improved command-line parsing. Much fuller help. Much
402 # better internal structure. It's just BETTER. :)
404 # Revision 1.5  2001/07/30 00:04:48  richard
405 # Made the "init" prompting more friendly.
407 # Revision 1.4  2001/07/29 07:01:39  richard
408 # Added vim command to all source so that we don't get no steenkin' tabs :)
410 # Revision 1.3  2001/07/23 08:45:28  richard
411 # ok, so now "./roundup-admin init" will ask questions in an attempt to get a
412 # workable instance_home set up :)
413 # _and_ anydbm has had its first test :)
415 # Revision 1.2  2001/07/23 08:20:44  richard
416 # Moved over to using marshal in the bsddb and anydbm backends.
417 # roundup-admin now has a "freshen" command that'll load/save all nodes (not
418 #  retired - mod hyperdb.Class.list() so it lists retired nodes)
420 # Revision 1.1  2001/07/23 03:46:48  richard
421 # moving the bin files to facilitate out-of-the-boxness
423 # Revision 1.1  2001/07/22 11:15:45  richard
424 # More Grande Splite stuff
427 # vim: set filetype=python ts=4 sw=4 et si