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
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())
395 #
396 # $Log: not supported by cvs2svn $
397 # Revision 1.7 2001/07/30 01:28:46 richard
398 # Bugfixes
399 #
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. :)
403 #
404 # Revision 1.5 2001/07/30 00:04:48 richard
405 # Made the "init" prompting more friendly.
406 #
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 :)
409 #
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 :)
414 #
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)
419 #
420 # Revision 1.1 2001/07/23 03:46:48 richard
421 # moving the bin files to facilitate out-of-the-boxness
422 #
423 # Revision 1.1 2001/07/22 11:15:45 richard
424 # More Grande Splite stuff
425 #
426 #
427 # vim: set filetype=python ts=4 sw=4 et si