comparison roundup-admin @ 301:7901b58cfae8

Oops, committed the admin script with the wierd #! line. Also, made the thing into a class to reduce parameter passing. Nuked the leading whitespace from the help __doc__ displays too.
author Richard Jones <richard@users.sourceforge.net>
date Thu, 18 Oct 2001 02:16:42 +0000
parents fd9835c1e58d
children d1fb3fcdb11b
comparison
equal deleted inserted replaced
300:590abb8e808c 301:7901b58cfae8
1 #! /Users/builder/bin/python 1 #! /usr/bin/env python
2 # 2 #
3 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) 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 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 5 # under the same terms as Python, so long as this copyright message and
6 # disclaimer are retained in their original form. 6 # disclaimer are retained in their original form.
14 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 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" 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, 16 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
18 # 18 #
19 # $Id: roundup-admin,v 1.33 2001-10-17 23:13:19 richard Exp $ 19 # $Id: roundup-admin,v 1.34 2001-10-18 02:16:42 richard Exp $
20 20
21 import sys 21 import sys
22 if int(sys.version[0]) < 2: 22 if int(sys.version[0]) < 2:
23 print 'Roundup requires python 2.0 or later.' 23 print 'Roundup requires python 2.0 or later.'
24 sys.exit(1) 24 sys.exit(1)
29 except ImportError: 29 except ImportError:
30 csv = None 30 csv = None
31 from roundup import date, hyperdb, roundupdb, init, password 31 from roundup import date, hyperdb, roundupdb, init, password
32 import roundup.instance 32 import roundup.instance
33 33
34 def usage(message=''): 34 class AdminTool:
35 if message: message = 'Problem: '+message+'\n' 35
36 print '''%sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments> 36 def __init__(self):
37 self.commands = {}
38 for k, v in AdminTool.__dict__.items():
39 if k[:3] == 'do_':
40 self.commands[k[3:]] = v
41 self.help = {}
42 for k, v in AdminTool.__dict__.items():
43 if k[:5] == 'help_':
44 self.help[k[5:]] = v
45
46 def usage(message=''):
47 if message: message = 'Problem: '+message+'\n'
48 print '''%sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments>
37 49
38 Help: 50 Help:
39 roundup-admin -h 51 roundup-admin -h
40 roundup-admin help -- this help 52 roundup-admin help -- this help
41 roundup-admin help <command> -- command-specific help 53 roundup-admin help <command> -- command-specific help
42 roundup-admin help all -- all available help 54 roundup-admin help all -- all available help
43 Options: 55 Options:
44 -i instance home -- specify the issue tracker "home directory" to administer 56 -i instance home -- specify the issue tracker "home directory" to administer
45 -u -- the user[:password] to use for commands 57 -u -- the user[:password] to use for commands
46 -c -- when outputting lists of data, just comma-separate them'''%message 58 -c -- when outputting lists of data, just comma-separate them'''%message
47 help_commands() 59 self.help_commands()
48 60
49 def help_commands(): 61 def help_commands(self):
50 print 'Commands:', 62 print 'Commands:',
51 commands = [''] 63 commands = ['']
52 for command in figureCommands().values(): 64 for command in self.commands.values():
53 h = command.__doc__.split('\n')[0] 65 h = command.__doc__.split('\n')[0]
54 commands.append(h[7:]) 66 commands.append(h[7:])
55 commands.sort() 67 commands.sort()
56 print '\n '.join(commands) 68 print '\n '.join(commands)
57 69
58 def help_all(): 70 def help_all(self):
59 print ''' 71 print '''
60 All commands (except help) require an instance specifier. This is just the path 72 All commands (except help) require an instance specifier. This is just the path
61 to the roundup instance you're working with. A roundup instance is where 73 to the roundup instance you're working with. A roundup instance is where
62 roundup keeps the database and configuration file that defines an issue 74 roundup keeps the database and configuration file that defines an issue
63 tracker. It may be thought of as the issue tracker's "home directory". It may 75 tracker. It may be thought of as the issue tracker's "home directory". It may
64 be specified in the environment variable ROUNDUP_INSTANCE or on the command 76 be specified in the environment variable ROUNDUP_INSTANCE or on the command
103 "8:47:11" means <Date yyyy-mm-dd.13:47:11> 115 "8:47:11" means <Date yyyy-mm-dd.13:47:11>
104 "." means "right now" 116 "." means "right now"
105 117
106 Command help: 118 Command help:
107 ''' 119 '''
108 for name, command in figureCommands().items(): 120 for name, command in self.commands.items():
109 print '%s:'%name 121 print '%s:'%name
110 print ' ',command.__doc__ 122 print ' ',command.__doc__
111 123
112 def do_help(args): 124 def do_help(self, args, nl_re=re.compile('[\r\n]'),
113 '''Usage: help topic 125 indent_re=re.compile(r'^(\s+)\S+')):
114 Give help about topic. 126 '''Usage: help topic
115 127 Give help about topic.
116 commands -- list commands 128
117 <command> -- help specific to a command 129 commands -- list commands
118 initopts -- init command options 130 <command> -- help specific to a command
119 all -- all available help 131 initopts -- init command options
120 ''' 132 all -- all available help
121 help = figureHelp().get(args[0], None) 133 '''
122 if help: 134 help = self.help.get(args[0], None)
123 help() 135 if help:
124 return 136 help(self)
125 help = figureCommands().get(args[0], None) 137 return
126 if help: 138 help = self.commands.get(args[0], None)
127 print help.__doc__ 139 if help:
128 else: 140 # display the help, removing the docsring indent
129 print 'Sorry, no help for "%s"'%args[0] 141 lines = nl_re.split(help.__doc__)
130 142 print lines[0]
131 def help_initopts(): 143 indent = indent_re.match(lines[1])
132 import roundup.templates 144 if indent: indent = len(indent.group(1))
133 templates = roundup.templates.listTemplates() 145 for line in lines[1:]:
134 print 'Templates:', ', '.join(templates) 146 if indent:
135 import roundup.backends 147 print line[indent:]
136 backends = roundup.backends.__all__ 148 else:
137 print 'Back ends:', ', '.join(backends) 149 print line
138 150 else:
139 151 print 'Sorry, no help for "%s"'%args[0]
140 def do_init(instance_home, args, comma_sep=0): 152
141 '''Usage: init [template [backend [admin password]]] 153 def help_initopts(self):
142 Initialise a new Roundup instance. 154 import roundup.templates
143 155 templates = roundup.templates.listTemplates()
144 The command will prompt for the instance home directory (if not supplied
145 through INSTANCE_HOME or the -i option. The template, backend and admin
146 password may be specified on the command-line as arguments, in that order.
147
148 See also initopts help.
149 '''
150 # select template
151 import roundup.templates
152 templates = roundup.templates.listTemplates()
153 template = len(args) > 1 and args[1] or ''
154 if template not in templates:
155 print 'Templates:', ', '.join(templates) 156 print 'Templates:', ', '.join(templates)
156 while template not in templates: 157 import roundup.backends
157 template = raw_input('Select template [extended]: ').strip() 158 backends = roundup.backends.__all__
158 if not template:
159 template = 'extended'
160
161 import roundup.backends
162 backends = roundup.backends.__all__
163 backend = len(args) > 2 and args[2] or ''
164 if backend not in backends:
165 print 'Back ends:', ', '.join(backends) 159 print 'Back ends:', ', '.join(backends)
166 while backend not in backends: 160
167 backend = raw_input('Select backend [anydbm]: ').strip() 161
168 if not backend: 162 def do_init(instance_home, args):
169 backend = 'anydbm' 163 '''Usage: init [template [backend [admin password]]]
170 if len(args) > 3: 164 Initialise a new Roundup instance.
171 adminpw = confirm = args[3] 165
172 else: 166 The command will prompt for the instance home directory (if not supplied
173 adminpw = '' 167 through INSTANCE_HOME or the -i option. The template, backend and admin
174 confirm = 'x' 168 password may be specified on the command-line as arguments, in that
175 while adminpw != confirm: 169 order.
176 adminpw = getpass.getpass('Admin Password: ') 170
177 confirm = getpass.getpass(' Confirm: ') 171 See also initopts help.
178 init.init(instance_home, template, backend, adminpw) 172 '''
179 return 0 173 # select template
180 174 import roundup.templates
181 175 templates = roundup.templates.listTemplates()
182 def do_get(db, args, comma_sep=0): 176 template = len(args) > 1 and args[1] or ''
183 '''Usage: get property designator[,designator]* 177 if template not in templates:
184 Get the given property of one or more designator(s). 178 print 'Templates:', ', '.join(templates)
185 179 while template not in templates:
186 Retrieves the property value of the nodes specified by the designators. 180 template = raw_input('Select template [extended]: ').strip()
187 ''' 181 if not template:
188 propname = args[0] 182 template = 'extended'
189 designators = string.split(args[1], ',') 183
190 l = [] 184 import roundup.backends
191 for designator in designators: 185 backends = roundup.backends.__all__
192 classname, nodeid = roundupdb.splitDesignator(designator) 186 backend = len(args) > 2 and args[2] or ''
193 if comma_sep: 187 if backend not in backends:
194 l.append(db.getclass(classname).get(nodeid, propname)) 188 print 'Back ends:', ', '.join(backends)
189 while backend not in backends:
190 backend = raw_input('Select backend [anydbm]: ').strip()
191 if not backend:
192 backend = 'anydbm'
193 if len(args) > 3:
194 adminpw = confirm = args[3]
195 else: 195 else:
196 print db.getclass(classname).get(nodeid, propname) 196 adminpw = ''
197 if comma_sep: 197 confirm = 'x'
198 print ','.join(l) 198 while adminpw != confirm:
199 return 0 199 adminpw = getpass.getpass('Admin Password: ')
200 200 confirm = getpass.getpass(' Confirm: ')
201 201 init.init(instance_home, template, backend, adminpw)
202 def do_set(db, args, comma_sep=0): 202 return 0
203 '''Usage: set designator[,designator]* propname=value ... 203
204 Set the given property of one or more designator(s). 204
205 205 def do_get(self, args):
206 Sets the property to the value for all designators given. 206 '''Usage: get property designator[,designator]*
207 ''' 207 Get the given property of one or more designator(s).
208 from roundup import hyperdb 208
209 209 Retrieves the property value of the nodes specified by the designators.
210 designators = string.split(args[0], ',') 210 '''
211 props = {} 211 propname = args[0]
212 for prop in args[1:]: 212 designators = string.split(args[1], ',')
213 key, value = prop.split('=') 213 l = []
214 props[key] = value 214 for designator in designators:
215 for designator in designators: 215 classname, nodeid = roundupdb.splitDesignator(designator)
216 classname, nodeid = roundupdb.splitDesignator(designator) 216 if self.comma_sep:
217 cl = db.getclass(classname) 217 l.append(self.db.getclass(classname).get(nodeid, propname))
218 properties = cl.getprops() 218 else:
219 for key, value in props.items(): 219 print self.db.getclass(classname).get(nodeid, propname)
220 if self.comma_sep:
221 print ','.join(l)
222 return 0
223
224
225 def do_set(self, args):
226 '''Usage: set designator[,designator]* propname=value ...
227 Set the given property of one or more designator(s).
228
229 Sets the property to the value for all designators given.
230 '''
231 from roundup import hyperdb
232
233 designators = string.split(args[0], ',')
234 props = {}
235 for prop in args[1:]:
236 key, value = prop.split('=')
237 props[key] = value
238 for designator in designators:
239 classname, nodeid = roundupdb.splitDesignator(designator)
240 cl = self.db.getclass(classname)
241 properties = cl.getprops()
242 for key, value in props.items():
243 type = properties[key]
244 if isinstance(type, hyperdb.String):
245 continue
246 elif isinstance(type, hyperdb.Password):
247 props[key] = password.Password(value)
248 elif isinstance(type, hyperdb.Date):
249 props[key] = date.Date(value)
250 elif isinstance(type, hyperdb.Interval):
251 props[key] = date.Interval(value)
252 elif isinstance(type, hyperdb.Link):
253 props[key] = value
254 elif isinstance(type, hyperdb.Multilink):
255 props[key] = value.split(',')
256 apply(cl.set, (nodeid, ), props)
257 return 0
258
259 def do_find(self, args):
260 '''Usage: find classname propname=value ...
261 Find the nodes of the given class with a given link property value.
262
263 Find the nodes of the given class with a given link property value. The
264 value may be either the nodeid of the linked node, or its key value.
265 '''
266 classname = args[0]
267 cl = self.db.getclass(classname)
268
269 # look up the linked-to class and get the nodeid that has the value
270 propname, value = args[1].split('=')
271 num_re = re.compile('^\d+$')
272 if not num_re.match(value):
273 propcl = cl.properties[propname]
274 if (not isinstance(propcl, hyperdb.Link) and not
275 isinstance(type, hyperdb.Multilink)):
276 print 'You may only "find" link properties'
277 return 1
278 propcl = self.db.getclass(propcl.classname)
279 value = propcl.lookup(value)
280
281 # now do the find
282 if self.comma_sep:
283 print ','.join(cl.find(**{propname: value}))
284 else:
285 print cl.find(**{propname: value})
286 return 0
287
288 def do_spec(self, args):
289 '''Usage: spec classname
290 Show the properties for a classname.
291
292 This lists the properties for a given class.
293 '''
294 classname = args[0]
295 cl = self.db.getclass(classname)
296 keyprop = cl.getkey()
297 for key, value in cl.properties.items():
298 if keyprop == key:
299 print '%s: %s (key property)'%(key, value)
300 else:
301 print '%s: %s'%(key, value)
302
303 def do_create(self, args):
304 '''Usage: create classname property=value ...
305 Create a new entry of a given class.
306
307 This creates a new entry of the given class using the property
308 name=value arguments provided on the command line after the "create"
309 command.
310 '''
311 from roundup import hyperdb
312
313 classname = args[0]
314 cl = self.db.getclass(classname)
315 props = {}
316 properties = cl.getprops(protected = 0)
317 if len(args) == 1:
318 # ask for the properties
319 for key, value in properties.items():
320 if key == 'id': continue
321 name = value.__class__.__name__
322 if isinstance(value , hyperdb.Password):
323 again = None
324 while value != again:
325 value = getpass.getpass('%s (Password): '%key.capitalize())
326 again = getpass.getpass(' %s (Again): '%key.capitalize())
327 if value != again: print 'Sorry, try again...'
328 if value:
329 props[key] = value
330 else:
331 value = raw_input('%s (%s): '%(key.capitalize(), name))
332 if value:
333 props[key] = value
334 else:
335 # use the args
336 for prop in args[1:]:
337 key, value = prop.split('=')
338 props[key] = value
339
340 # convert types
341 for key in props.keys():
220 type = properties[key] 342 type = properties[key]
221 if isinstance(type, hyperdb.String): 343 if isinstance(type, hyperdb.Date):
222 continue
223 elif isinstance(type, hyperdb.Password):
224 props[key] = password.Password(value)
225 elif isinstance(type, hyperdb.Date):
226 props[key] = date.Date(value) 344 props[key] = date.Date(value)
227 elif isinstance(type, hyperdb.Interval): 345 elif isinstance(type, hyperdb.Interval):
228 props[key] = date.Interval(value) 346 props[key] = date.Interval(value)
229 elif isinstance(type, hyperdb.Link): 347 elif isinstance(type, hyperdb.Password):
230 props[key] = value 348 props[key] = password.Password(value)
231 elif isinstance(type, hyperdb.Multilink): 349 elif isinstance(type, hyperdb.Multilink):
232 props[key] = value.split(',') 350 props[key] = value.split(',')
233 apply(cl.set, (nodeid, ), props) 351
234 return 0 352 if cl.getkey() and not props.has_key(cl.getkey()):
235 353 print "You must provide the '%s' property."%cl.getkey()
236 def do_find(db, args, comma_sep=0):
237 '''Usage: find classname propname=value ...
238 Find the nodes of the given class with a given link property value.
239
240 Find the nodes of the given class with a given link property value. The
241 value may be either the nodeid of the linked node, or its key value.
242 '''
243 classname = args[0]
244 cl = db.getclass(classname)
245
246 # look up the linked-to class and get the nodeid that has the value
247 propname, value = args[1].split('=')
248 num_re = re.compile('^\d+$')
249 if not num_re.match(value):
250 propcl = cl.properties[propname]
251 if (not isinstance(propcl, hyperdb.Link) and not
252 isinstance(type, hyperdb.Multilink)):
253 print 'You may only "find" link properties'
254 return 1
255 propcl = db.getclass(propcl.classname)
256 value = propcl.lookup(value)
257
258 # now do the find
259 if comma_sep:
260 print ','.join(cl.find(**{propname: value}))
261 else:
262 print cl.find(**{propname: value})
263 return 0
264
265 def do_spec(db, args, comma_sep=0):
266 '''Usage: spec classname
267 Show the properties for a classname.
268
269 This lists the properties for a given class.
270 '''
271 classname = args[0]
272 cl = db.getclass(classname)
273 keyprop = cl.getkey()
274 for key, value in cl.properties.items():
275 if keyprop == key:
276 print '%s: %s (key property)'%(key, value)
277 else: 354 else:
278 print '%s: %s'%(key, value) 355 print apply(cl.create, (), props)
279 356
280 def do_create(db, args, comma_sep=0): 357 return 0
281 '''Usage: create classname property=value ... 358
282 Create a new entry of a given class. 359 def do_list(self, args):
283 360 '''Usage: list classname [property]
284 This creates a new entry of the given class using the property 361 List the instances of a class.
285 name=value arguments provided on the command line after the "create" 362
286 command. 363 Lists all instances of the given class. If the property is not
287 ''' 364 specified, the "label" property is used. The label property is tried
288 from roundup import hyperdb 365 in order: the key, "name", "title" and then the first property,
289 366 alphabetically.
290 classname = args[0] 367 '''
291 cl = db.getclass(classname) 368 classname = args[0]
292 props = {} 369 cl = self.db.getclass(classname)
293 properties = cl.getprops(protected = 0) 370 if len(args) > 1:
294 if len(args) == 1: 371 key = args[1]
295 # ask for the properties 372 else:
296 for key, value in properties.items(): 373 key = cl.labelprop()
297 if key == 'id': continue 374 if self.comma_sep:
298 name = value.__class__.__name__ 375 print ','.join(cl.list())
299 if isinstance(value , hyperdb.Password): 376 else:
300 again = None 377 for nodeid in cl.list():
301 while value != again: 378 value = cl.get(nodeid, key)
302 value = getpass.getpass('%s (Password): '%key.capitalize()) 379 print "%4s: %s"%(nodeid, value)
303 again = getpass.getpass(' %s (Again): '%key.capitalize()) 380 return 0
304 if value != again: print 'Sorry, try again...' 381
305 if value: 382 def do_table(self, args):
306 props[key] = value 383 '''Usage: table classname [property[,property]*]
384 List the instances of a class in tabular form.
385
386 Lists all instances of the given class. If the properties are not
387 specified, all properties are displayed. By default, the column widths
388 are the width of the property names. The width may be explicitly defined
389 by defining the property as "name:width". For example::
390 roundup> table priority id,name:10
391 Id Name
392 1 fatal-bug
393 2 bug
394 3 usability
395 4 feature
396 '''
397 classname = args[0]
398 cl = self.db.getclass(classname)
399 if len(args) > 1:
400 prop_names = args[1].split(',')
401 else:
402 prop_names = cl.getprops().keys()
403 props = []
404 for name in prop_names:
405 if ':' in name:
406 name, width = name.split(':')
407 props.append((name, int(width)))
307 else: 408 else:
308 value = raw_input('%s (%s): '%(key.capitalize(), name)) 409 props.append((name, len(name)))
309 if value: 410
310 props[key] = value 411 print ' '.join([string.capitalize(name) for name, width in props])
311 else:
312 # use the args
313 for prop in args[1:]:
314 key, value = prop.split('=')
315 props[key] = value
316
317 # convert types
318 for key in props.keys():
319 type = properties[key]
320 if isinstance(type, hyperdb.Date):
321 props[key] = date.Date(value)
322 elif isinstance(type, hyperdb.Interval):
323 props[key] = date.Interval(value)
324 elif isinstance(type, hyperdb.Password):
325 props[key] = password.Password(value)
326 elif isinstance(type, hyperdb.Multilink):
327 props[key] = value.split(',')
328
329 if cl.getkey() and not props.has_key(cl.getkey()):
330 print "You must provide the '%s' property."%cl.getkey()
331 else:
332 print apply(cl.create, (), props)
333
334 return 0
335
336 def do_list(db, args, comma_sep=0):
337 '''Usage: list classname [property]
338 List the instances of a class.
339
340 Lists all instances of the given class. If the property is not
341 specified, the "label" property is used. The label property is tried
342 in order: the key, "name", "title" and then the first property,
343 alphabetically.
344 '''
345 classname = args[0]
346 cl = db.getclass(classname)
347 if len(args) > 1:
348 key = args[1]
349 else:
350 key = cl.labelprop()
351 if comma_sep:
352 print ','.join(cl.list())
353 else:
354 for nodeid in cl.list():
355 value = cl.get(nodeid, key)
356 print "%4s: %s"%(nodeid, value)
357 return 0
358
359 def do_table(db, args, comma_sep=None):
360 '''Usage: table classname [property[,property]*]
361 List the instances of a class in tabular form.
362
363 Lists all instances of the given class. If the properties are not
364 specified, all properties are displayed. By default, the column widths
365 are the width of the property names. The width may be explicitly defined
366 by defining the property as "name:width". For example::
367 roundup> table priority id,name:10
368 Id Name
369 1 fatal-bug
370 2 bug
371 3 usability
372 4 feature
373 '''
374 classname = args[0]
375 cl = db.getclass(classname)
376 if len(args) > 1:
377 prop_names = args[1].split(',')
378 else:
379 prop_names = cl.getprops().keys()
380 props = []
381 for name in prop_names:
382 if ':' in name:
383 name, width = name.split(':')
384 props.append((name, int(width)))
385 else:
386 props.append((name, len(name)))
387
388 print ' '.join([string.capitalize(name) for name, width in props])
389 for nodeid in cl.list():
390 l = []
391 for name, width in props:
392 if name != 'id':
393 value = str(cl.get(nodeid, name))
394 else:
395 value = str(nodeid)
396 f = '%%-%ds'%width
397 l.append(f%value[:width])
398 print ' '.join(l)
399 return 0
400
401 def do_history(db, args, comma_sep=0):
402 '''Usage: history designator
403 Show the history entries of a designator.
404
405 Lists the journal entries for the node identified by the designator.
406 '''
407 classname, nodeid = roundupdb.splitDesignator(args[0])
408 # TODO: handle the -c option?
409 print db.getclass(classname).history(nodeid)
410 return 0
411
412 def do_retire(db, args, comma_sep=0):
413 '''Usage: retire designator[,designator]*
414 Retire the node specified by designator.
415
416 This action indicates that a particular node is not to be retrieved by
417 the list or find commands, and its key value may be re-used.
418 '''
419 designators = string.split(args[0], ',')
420 for designator in designators:
421 classname, nodeid = roundupdb.splitDesignator(designator)
422 db.getclass(classname).retire(nodeid)
423 return 0
424
425 def do_export(db, args, comma_sep=0):
426 '''Usage: export class[,class] destination_dir
427 Export the database to tab-separated-value files.
428
429 This action exports the current data from the database into
430 tab-separated-value files that are placed in the nominated destination
431 directory. The journals are not exported.
432 '''
433 if len(args) < 2:
434 print do_export.__doc__
435 return 1
436 classes = string.split(args[0], ',')
437 dir = args[1]
438
439 # use the csv parser if we can - it's faster
440 if csv is not None:
441 p = csv.parser(field_sep=':')
442
443 # do all the classes specified
444 for classname in classes:
445 cl = db.getclass(classname)
446 f = open(os.path.join(dir, classname+'.csv'), 'w')
447 f.write(string.join(cl.properties.keys(), ':') + '\n')
448
449 # all nodes for this class
450 properties = cl.properties.items()
451 for nodeid in cl.list(): 412 for nodeid in cl.list():
452 l = [] 413 l = []
453 for prop, type in properties: 414 for name, width in props:
454 value = cl.get(nodeid, prop) 415 if name != 'id':
455 # convert data where needed 416 value = str(cl.get(nodeid, name))
417 else:
418 value = str(nodeid)
419 f = '%%-%ds'%width
420 l.append(f%value[:width])
421 print ' '.join(l)
422 return 0
423
424 def do_history(self, args):
425 '''Usage: history designator
426 Show the history entries of a designator.
427
428 Lists the journal entries for the node identified by the designator.
429 '''
430 classname, nodeid = roundupdb.splitDesignator(args[0])
431 # TODO: handle the -c option?
432 print self.db.getclass(classname).history(nodeid)
433 return 0
434
435 def do_retire(self, args):
436 '''Usage: retire designator[,designator]*
437 Retire the node specified by designator.
438
439 This action indicates that a particular node is not to be retrieved by
440 the list or find commands, and its key value may be re-used.
441 '''
442 designators = string.split(args[0], ',')
443 for designator in designators:
444 classname, nodeid = roundupdb.splitDesignator(designator)
445 self.db.getclass(classname).retire(nodeid)
446 return 0
447
448 def do_export(self, args):
449 '''Usage: export class[,class] destination_dir
450 Export the database to tab-separated-value files.
451
452 This action exports the current data from the database into
453 tab-separated-value files that are placed in the nominated destination
454 directory. The journals are not exported.
455 '''
456 if len(args) < 2:
457 print do_export.__doc__
458 return 1
459 classes = string.split(args[0], ',')
460 dir = args[1]
461
462 # use the csv parser if we can - it's faster
463 if csv is not None:
464 p = csv.parser(field_sep=':')
465
466 # do all the classes specified
467 for classname in classes:
468 cl = self.db.getclass(classname)
469 f = open(os.path.join(dir, classname+'.csv'), 'w')
470 f.write(string.join(cl.properties.keys(), ':') + '\n')
471
472 # all nodes for this class
473 properties = cl.properties.items()
474 for nodeid in cl.list():
475 l = []
476 for prop, type in properties:
477 value = cl.get(nodeid, prop)
478 # convert data where needed
479 if isinstance(type, hyperdb.Date):
480 value = value.get_tuple()
481 elif isinstance(type, hyperdb.Interval):
482 value = value.get_tuple()
483 elif isinstance(type, hyperdb.Password):
484 value = str(value)
485 l.append(repr(value))
486
487 # now write
488 if csv is not None:
489 f.write(p.join(l) + '\n')
490 else:
491 # escape the individual entries to they're valid CSV
492 m = []
493 for entry in l:
494 if '"' in entry:
495 entry = '""'.join(entry.split('"'))
496 if ':' in entry:
497 entry = '"%s"'%entry
498 m.append(entry)
499 f.write(':'.join(m) + '\n')
500 return 0
501
502 def do_import(self, args):
503 '''Usage: import class file
504 Import the contents of the tab-separated-value file.
505
506 The file must define the same properties as the class (including having
507 a "header" line with those property names.) The new nodes are added to
508 the existing database - if you want to create a new database using the
509 imported data, then create a new database (or, tediously, retire all
510 the old data.)
511 '''
512 if len(args) < 2:
513 print do_import.__doc__
514 return 1
515 if csv is None:
516 print 'Sorry, you need the csv module to use this function.'
517 print 'Get it from: http://www.object-craft.com.au/projects/csv/'
518 return 1
519
520 from roundup import hyperdb
521
522 # ensure that the properties and the CSV file headings match
523 cl = self.db.getclass(args[0])
524 f = open(args[1])
525 p = csv.parser(field_sep=':')
526 file_props = p.parse(f.readline())
527 props = cl.properties.keys()
528 m = file_props[:]
529 m.sort()
530 props.sort()
531 if m != props:
532 print 'Import file doesn\'t define the same properties as "%s".'%args[0]
533 return 1
534
535 # loop through the file and create a node for each entry
536 n = range(len(props))
537 while 1:
538 line = f.readline()
539 if not line: break
540
541 # parse lines until we get a complete entry
542 while 1:
543 l = p.parse(line)
544 if l: break
545
546 # make the new node's property map
547 d = {}
548 for i in n:
549 # Use eval to reverse the repr() used to output the CSV
550 value = eval(l[i])
551 # Figure the property for this column
552 key = file_props[i]
553 type = cl.properties[key]
554 # Convert for property type
456 if isinstance(type, hyperdb.Date): 555 if isinstance(type, hyperdb.Date):
457 value = value.get_tuple() 556 value = date.Date(value)
458 elif isinstance(type, hyperdb.Interval): 557 elif isinstance(type, hyperdb.Interval):
459 value = value.get_tuple() 558 value = date.Interval(value)
460 elif isinstance(type, hyperdb.Password): 559 elif isinstance(type, hyperdb.Password):
461 value = str(value) 560 pwd = password.Password()
462 l.append(repr(value)) 561 pwd.unpack(value)
463 562 value = pwd
464 # now write 563 if value is not None:
465 if csv is not None: 564 d[key] = value
466 f.write(p.join(l) + '\n') 565
467 else: 566 # and create the new node
468 # escape the individual entries to they're valid CSV 567 apply(cl.create, (), d)
469 m = [] 568 return 0
470 for entry in l: 569
471 if '"' in entry:
472 entry = '""'.join(entry.split('"'))
473 if ':' in entry:
474 entry = '"%s"'%entry
475 m.append(entry)
476 f.write(':'.join(m) + '\n')
477 return 0
478
479 def do_import(db, args, comma_sep=0):
480 '''Usage: import class file
481 Import the contents of the tab-separated-value file.
482
483 The file must define the same properties as the class (including having
484 a "header" line with those property names.) The new nodes are added to
485 the existing database - if you want to create a new database using the
486 imported data, then create a new database (or, tediously, retire all
487 the old data.)
488 '''
489 if len(args) < 2:
490 print do_import.__doc__
491 return 1
492 if csv is None:
493 print 'Sorry, you need the csv module to use this function.'
494 print 'Get it from: http://www.object-craft.com.au/projects/csv/'
495 return 1
496
497 from roundup import hyperdb
498
499 # ensure that the properties and the CSV file headings match
500 cl = db.getclass(args[0])
501 f = open(args[1])
502 p = csv.parser(field_sep=':')
503 file_props = p.parse(f.readline())
504 props = cl.properties.keys()
505 m = file_props[:]
506 m.sort()
507 props.sort()
508 if m != props:
509 print 'Import file doesn\'t define the same properties as "%s".'%args[0]
510 return 1
511
512 # loop through the file and create a node for each entry
513 n = range(len(props))
514 while 1:
515 line = f.readline()
516 if not line: break
517
518 # parse lines until we get a complete entry
519 while 1:
520 l = p.parse(line)
521 if l: break
522
523 # make the new node's property map
524 d = {}
525 for i in n:
526 # Use eval to reverse the repr() used to output the CSV
527 value = eval(l[i])
528 # Figure the property for this column
529 key = file_props[i]
530 type = cl.properties[key]
531 # Convert for property type
532 if isinstance(type, hyperdb.Date):
533 value = date.Date(value)
534 elif isinstance(type, hyperdb.Interval):
535 value = date.Interval(value)
536 elif isinstance(type, hyperdb.Password):
537 pwd = password.Password()
538 pwd.unpack(value)
539 value = pwd
540 if value is not None:
541 d[key] = value
542
543 # and create the new node
544 apply(cl.create, (), d)
545 return 0
546
547 def figureCommands():
548 d = {}
549 for k, v in globals().items():
550 if k[:3] == 'do_':
551 d[k[3:]] = v
552 return d
553
554 def figureHelp():
555 d = {}
556 for k, v in globals().items():
557 if k[:5] == 'help_':
558 d[k[5:]] = v
559 return d
560
561 class AdminTool:
562 def run_command(self, args): 570 def run_command(self, args):
563 '''Run a single command 571 '''Run a single command
564 ''' 572 '''
565 command = args[0] 573 command = args[0]
566 574
567 # handle help now 575 # handle help now
568 if command == 'help': 576 if command == 'help':
569 if len(args)>1: 577 if len(args)>1:
570 do_help(args[1:]) 578 self.do_help(args[1:])
571 return 0 579 return 0
572 do_help(['help']) 580 self.do_help(['help'])
573 return 0 581 return 0
574 if command == 'morehelp': 582 if command == 'morehelp':
575 do_help(['help']) 583 self.do_help(['help'])
576 help_commands() 584 self.help_commands()
577 help_all() 585 self.help_all()
578 return 0 586 return 0
579 587
580 # make sure we have an instance_home 588 # make sure we have an instance_home
581 while not self.instance_home: 589 while not self.instance_home:
582 self.instance_home = raw_input('Enter instance home: ').strip() 590 self.instance_home = raw_input('Enter instance home: ').strip()
583 591
584 # before we open the db, we may be doing an init 592 # before we open the db, we may be doing an init
585 if command == 'init': 593 if command == 'init':
586 return do_init(self.instance_home, args) 594 return self.do_init(self.instance_home, args)
587 595
588 function = figureCommands().get(command, None) 596 function = self.commands.get(command, None)
589 597
590 # not a valid command 598 # not a valid command
591 if function is None: 599 if function is None:
592 print 'Unknown command "%s" ("help commands" for a list)'%command 600 print 'Unknown command "%s" ("help commands" for a list)'%command
593 return 1 601 return 1
594 602
595 # get the instance 603 # get the instance
596 instance = roundup.instance.open(self.instance_home) 604 instance = roundup.instance.open(self.instance_home)
597 db = instance.open('admin') 605 self.db = instance.open('admin')
598 606
599 if len(args) < 2: 607 if len(args) < 2:
600 print function.__doc__ 608 print function.__doc__
601 return 1 609 return 1
602 610
603 # do the command 611 # do the command
604 try: 612 try:
605 return function(db, args[1:], comma_sep=self.comma_sep) 613 return function(args[1:])
606 finally: 614 finally:
607 db.close() 615 self.db.close()
608 616
609 return 1 617 return 1
610 618
611 def interactive(self, ws_re=re.compile(r'\s+')): 619 def interactive(self, ws_re=re.compile(r'\s+')):
612 '''Run in an interactive mode 620 '''Run in an interactive mode
661 tool = AdminTool() 669 tool = AdminTool()
662 sys.exit(tool.main()) 670 sys.exit(tool.main())
663 671
664 # 672 #
665 # $Log: not supported by cvs2svn $ 673 # $Log: not supported by cvs2svn $
674 # Revision 1.33 2001/10/17 23:13:19 richard
675 # Did a fair bit of work on the admin tool. Now has an extra command "table"
676 # which displays node information in a tabular format. Also fixed import and
677 # export so they work. Removed freshen.
678 # Fixed quopri usage in mailgw from bug reports.
679 #
666 # Revision 1.32 2001/10/17 06:57:29 richard 680 # Revision 1.32 2001/10/17 06:57:29 richard
667 # Interactive startup blurb - need to figure how to get the version in there. 681 # Interactive startup blurb - need to figure how to get the version in there.
668 # 682 #
669 # Revision 1.31 2001/10/17 06:17:26 richard 683 # Revision 1.31 2001/10/17 06:17:26 richard
670 # Now with readline support :) 684 # Now with readline support :)

Roundup Issue Tracker: http://roundup-tracker.org/