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