comparison roundup/admin.py @ 6638:e1588ae185dc issue2550923_computed_property

merge from default branch. Fix travis.ci so CI builds don't error out
author John Rouillard <rouilj@ieee.org>
date Thu, 21 Apr 2022 16:54:17 -0400
parents d4371b131c9c
children 408fd477761f
comparison
equal deleted inserted replaced
6508:85db90cc1705 6638:e1588ae185dc
21 """ 21 """
22 from __future__ import print_function 22 from __future__ import print_function
23 23
24 __docformat__ = 'restructuredtext' 24 __docformat__ = 'restructuredtext'
25 25
26 import csv, getopt, getpass, os, re, shutil, sys, operator 26 import csv, getopt, getpass, operator, os, re, shutil, sys
27 27
28 from roundup import date, hyperdb, init, password, token 28 from roundup import date, hyperdb, init, password, token
29 from roundup import __version__ as roundup_version 29 from roundup import __version__ as roundup_version
30 import roundup.instance 30 import roundup.instance
31 from roundup.configuration import CoreConfig, NoConfigError, UserConfig 31 from roundup.configuration import (CoreConfig, NoConfigError,
32 ParsingOptionError, UserConfig)
32 from roundup.i18n import _ 33 from roundup.i18n import _
33 from roundup.exceptions import UsageError 34 from roundup.exceptions import UsageError
34 from roundup.anypy.my_input import my_input 35 from roundup.anypy.my_input import my_input
35 from roundup.anypy.strings import repr_export 36 from roundup.anypy.strings import repr_export
36 37
49 50
50 def get(self, key, default=_marker): 51 def get(self, key, default=_marker):
51 if key in self.data: 52 if key in self.data:
52 return [(key, self.data[key])] 53 return [(key, self.data[key])]
53 keylist = sorted(self.data) 54 keylist = sorted(self.data)
54 l = [] 55 matching_keys = []
55 for ki in keylist: 56 for ki in keylist:
56 if ki.startswith(key): 57 if ki.startswith(key):
57 l.append((ki, self.data[ki])) 58 matching_keys.append((ki, self.data[ki]))
58 if not l and default is self._marker: 59 if not matching_keys and default is self._marker:
59 raise KeyError(key) 60 raise KeyError(key)
60 return l 61 # FIXME: what happens if default is not self._marker but
62 # there are no matching keys? Should (default, self.data[default])
63 # be returned???
64 return matching_keys
61 65
62 66
63 class AdminTool: 67 class AdminTool:
64 """ A collection of methods used in maintaining Roundup trackers. 68 """ A collection of methods used in maintaining Roundup trackers.
65 69
99 103
100 The args list is specified as ``prop=value prop=value ...``. 104 The args list is specified as ``prop=value prop=value ...``.
101 """ 105 """
102 props = {} 106 props = {}
103 for arg in args: 107 for arg in args:
104 l = arg.split('=', 1) 108 key_val = arg.split('=', 1)
105 # if = not in string, will return one element 109 # if = not in string, will return one element
106 if len(l) < 2: 110 if len(key_val) < 2:
107 raise UsageError(_('argument "%(arg)s" not propname=value') % 111 raise UsageError(_('argument "%(arg)s" not propname=value') %
108 locals()) 112 locals())
109 key, value = l 113 key, value = key_val
110 if value: 114 if value:
111 props[key] = value 115 props[key] = value
112 else: 116 else:
113 props[key] = None 117 props[key] = None
114 return props 118 return props
115 119
116 def usage(self, message=''): 120 def usage(self, message=''):
117 """ Display a simple usage message. 121 """ Display a simple usage message.
118 """ 122 """
119 if message: 123 if message:
120 message = _('Problem: %(message)s\n\n')% locals() 124 message = _('Problem: %(message)s\n\n') % locals()
121 sys.stdout.write(_("""%(message)sUsage: roundup-admin [options] [<command> <arguments>] 125 sys.stdout.write(_("""%(message)sUsage: roundup-admin [options] [<command> <arguments>]
122 126
123 Options: 127 Options:
124 -i instance home -- specify the issue tracker "home directory" to administer 128 -i instance home -- specify the issue tracker "home directory" to administer
125 -u -- the user[:password] to use for commands (default admin) 129 -u -- the user[:password] to use for commands (default admin)
265 self.help[topic]() 269 self.help[topic]()
266 return 0 270 return 0
267 271
268 # try command docstrings 272 # try command docstrings
269 try: 273 try:
270 l = self.commands.get(topic) 274 cmd_docs = self.commands.get(topic)
271 except KeyError: 275 except KeyError:
272 print(_('Sorry, no help for "%(topic)s"') % locals()) 276 print(_('Sorry, no help for "%(topic)s"') % locals())
273 return 1 277 return 1
274 278
275 # display the help for each match, removing the docstring indent 279 # display the help for each match, removing the docstring indent
276 for _name, help in l: 280 for _name, help in cmd_docs:
277 lines = nl_re.split(_(help.__doc__)) 281 lines = nl_re.split(_(help.__doc__))
278 print(lines[0]) 282 print(lines[0])
279 indent = indent_re.match(lines[1]) 283 indent = indent_re.match(lines[1])
280 if indent: indent = len(indent.group(1)) 284 if indent: indent = len(indent.group(1))
281 for line in lines[1:]: 285 for line in lines[1:]:
293 1. <roundup.admin.__file__>/../../share/roundup/templates/* 297 1. <roundup.admin.__file__>/../../share/roundup/templates/*
294 this is where they will be if we installed an egg via easy_install 298 this is where they will be if we installed an egg via easy_install
295 2. <prefix>/share/roundup/templates/* 299 2. <prefix>/share/roundup/templates/*
296 this should be the standard place to find them when Roundup is 300 this should be the standard place to find them when Roundup is
297 installed 301 installed
298 3. <roundup.admin.__file__>/../templates/* 302 3. <roundup.admin.__file__>/../../<sys.prefix>/share/\
303 roundup/templates/* which is where they will be found if
304 roundup is installed as a wheel using pip install
305 4. <roundup.admin.__file__>/../templates/*
299 this will be used if Roundup's run in the distro (aka. source) 306 this will be used if Roundup's run in the distro (aka. source)
300 directory 307 directory
301 4. <current working dir>/* 308 5. <current working dir>/*
302 this is for when someone unpacks a 3rd-party template 309 this is for when someone unpacks a 3rd-party template
303 5. <current working dir> 310 6. <current working dir>
304 this is for someone who "cd"s to the 3rd-party template dir 311 this is for someone who "cd"s to the 3rd-party template dir
305 """ 312 """
306 # OK, try <prefix>/share/roundup/templates 313 # OK, try <prefix>/share/roundup/templates
307 # and <egg-directory>/share/roundup/templates 314 # and <egg-directory>/share/roundup/templates
308 # -- this module (roundup.admin) will be installed in something 315 # -- this module (roundup.admin) will be installed in something
322 tdir = os.path.join(path, 'share', 'roundup', 'templates') 329 tdir = os.path.join(path, 'share', 'roundup', 'templates')
323 if os.path.isdir(tdir): 330 if os.path.isdir(tdir):
324 templates = init.listTemplates(tdir) 331 templates = init.listTemplates(tdir)
325 break 332 break
326 333
334 # search for data files parallel to the roundup
335 # install dir. E.G. a wheel install
336 # use roundup.__path__ and go up a level then use sys.prefix
337 # to create a base path for searching.
338
339 import sys
340 # __file__ should be something like:
341 # /usr/local/lib/python3.10/site-packages/roundup/admin.py
342 # os.prefix should be /usr, /usr/local or root of virtualenv
343 # strip leading / to make os.path.join work right.
344 path = __file__
345 for _N in 1, 2:
346 path = os.path.dirname(path)
347 # path is /usr/local/lib/python3.10/site-packages
348 tdir = os.path.join(path, sys.prefix[1:], 'share',
349 'roundup', 'templates')
350 if os.path.isdir(tdir):
351 templates.update(init.listTemplates(tdir))
352
327 # OK, now try as if we're in the roundup source distribution 353 # OK, now try as if we're in the roundup source distribution
328 # directory, so this module will be in .../roundup-*/roundup/admin.py 354 # directory, so this module will be in .../roundup-*/roundup/admin.py
329 # and we're interested in the .../roundup-*/ part. 355 # and we're interested in the .../roundup-*/ part.
330 path = __file__ 356 path = __file__
331 for _i in range(2): 357 for _i in range(2):
433 459
434 # load config_ini.ini from template if it exists. 460 # load config_ini.ini from template if it exists.
435 # it sets parameters like template_engine that are 461 # it sets parameters like template_engine that are
436 # template specific. 462 # template specific.
437 template_config = UserConfig(templates[template]['path'] + 463 template_config = UserConfig(templates[template]['path'] +
438 "/config_ini.ini") 464 "/config_ini.ini")
439 for k in template_config.keys(): 465 for k in template_config.keys():
440 if k == 'HOME': # ignore home. It is a default param. 466 if k == 'HOME': # ignore home. It is a default param.
441 continue 467 continue
442 defns[k] = template_config[k] 468 defns[k] = template_config[k]
443 469
581 """ 607 """
582 if len(args) < 2: 608 if len(args) < 2:
583 raise UsageError(_('Not enough arguments supplied')) 609 raise UsageError(_('Not enough arguments supplied'))
584 propname = args[0] 610 propname = args[0]
585 designators = args[1].split(',') 611 designators = args[1].split(',')
586 l = [] 612 linked_props = []
587 for designator in designators: 613 for designator in designators:
588 # decode the node designator 614 # decode the node designator
589 try: 615 try:
590 classname, nodeid = hyperdb.splitDesignator(designator) 616 classname, nodeid = hyperdb.splitDesignator(designator)
591 except hyperdb.DesignatorError as message: 617 except hyperdb.DesignatorError as message:
617 ' Multilink or Link so -d flag does not ' 643 ' Multilink or Link so -d flag does not '
618 'apply.') % propname) 644 'apply.') % propname)
619 propclassname = self.db.getclass(property.classname).classname 645 propclassname = self.db.getclass(property.classname).classname
620 id = cl.get(nodeid, propname) 646 id = cl.get(nodeid, propname)
621 for i in id: 647 for i in id:
622 l.append(propclassname + i) 648 linked_props.append(propclassname + i)
623 else: 649 else:
624 id = cl.get(nodeid, propname) 650 id = cl.get(nodeid, propname)
625 for i in id: 651 for i in id:
626 l.append(i) 652 linked_props.append(i)
627 else: 653 else:
628 if self.print_designator: 654 if self.print_designator:
629 properties = cl.getprops() 655 properties = cl.getprops()
630 property = properties[propname] 656 property = properties[propname]
631 if not (isinstance(property, hyperdb.Multilink) or 657 if not (isinstance(property, hyperdb.Multilink) or
644 '"%(nodeid)s"') % locals()) 670 '"%(nodeid)s"') % locals())
645 except KeyError: 671 except KeyError:
646 raise UsageError(_('no such %(classname)s property ' 672 raise UsageError(_('no such %(classname)s property '
647 '"%(propname)s"') % locals()) 673 '"%(propname)s"') % locals())
648 if self.separator: 674 if self.separator:
649 print(self.separator.join(l)) 675 print(self.separator.join(linked_props))
650 676
651 return 0 677 return 0
652 678
653 def do_set(self, args): 679 def do_set(self, args):
654 ''"""Usage: set items property=value property=value ... 680 ''"""Usage: set items property=value property=value ...
741 # multiple , separated values become a list 767 # multiple , separated values become a list
742 for propname, value in props.items(): 768 for propname, value in props.items():
743 if ',' in value: 769 if ',' in value:
744 values = value.split(',') 770 values = value.split(',')
745 else: 771 else:
746 values = [ value ] 772 values = [value]
747 773
748 props[propname] = [] 774 props[propname] = []
749 # start handling transitive props 775 # start handling transitive props
750 # given filter issue assignedto.roles=Admin 776 # given filter issue assignedto.roles=Admin
751 # start at issue 777 # start at issue
752 curclass = cl 778 curclass = cl
753 lastprop = propname # handle case 'issue assignedto=admin' 779 lastprop = propname # handle case 'issue assignedto=admin'
754 if '.' in propname: 780 if '.' in propname:
755 # start splitting transitive prop into components 781 # start splitting transitive prop into components
756 # we end when we have no more links 782 # we end when we have no more links
757 for pn in propname.split('.'): 783 for pn in propname.split('.'):
758 try: 784 try:
759 lastprop=pn # get current component 785 lastprop = pn # get current component
760 # get classname for this link 786 # get classname for this link
761 try: 787 try:
762 curclassname = curclass.getprops()[pn].classname 788 curclassname = curclass.getprops()[pn].classname
763 except KeyError: 789 except KeyError:
764 raise UsageError(_("Class %(curclassname)s has " 790 raise UsageError(_("Class %(curclassname)s has "
777 803
778 # now do the filter 804 # now do the filter
779 try: 805 try:
780 id = [] 806 id = []
781 designator = [] 807 designator = []
782 props = { "filterspec": props } 808 props = {"filterspec": props}
783 809
784 if self.separator: 810 if self.separator:
785 if self.print_designator: 811 if self.print_designator:
786 id = cl.filter(None, **props) 812 id = cl.filter(None, **props)
787 for i in id: 813 for i in id:
965 991
966 # convert types 992 # convert types
967 for propname in props: 993 for propname in props:
968 try: 994 try:
969 props[propname] = hyperdb.rawToHyperdb(self.db, cl, None, 995 props[propname] = hyperdb.rawToHyperdb(self.db, cl, None,
970 propname, props[propname]) 996 propname, props[propname])
971 except hyperdb.HyperdbValueError as message: 997 except hyperdb.HyperdbValueError as message:
972 raise UsageError(message) 998 raise UsageError(message)
973 999
974 # check for the key property 1000 # check for the key property
975 propname = cl.getkey() 1001 propname = cl.getkey()
976 if propname and propname not in props: 1002 if propname and propname not in props:
977 raise UsageError(_('you must provide the "%(propname)s" ' 1003 raise UsageError(_('you must provide the "%(propname)s" '
978 'property.') % locals()) 1004 'property.') % locals())
979 1005
980 # do the actual create 1006 # do the actual create
981 try: 1007 try:
982 sys.stdout.write(cl.create(**props) + '\n') 1008 sys.stdout.write(cl.create(**props) + '\n')
983 except (TypeError, IndexError, ValueError) as message: 1009 except (TypeError, IndexError, ValueError) as message:
1097 props = [] 1123 props = []
1098 for spec in prop_names: 1124 for spec in prop_names:
1099 if ':' in spec: 1125 if ':' in spec:
1100 name, width = spec.split(':') 1126 name, width = spec.split(':')
1101 if width == '': 1127 if width == '':
1102 # spec includes trailing :, use label/name width 1128 # spec includes trailing :, use label/name width
1103 props.append((name, len(name))) 1129 props.append((name, len(name)))
1104 else: 1130 else:
1105 try: 1131 try:
1106 props.append((name, int(width))) 1132 props.append((name, int(width)))
1107 except ValueError: 1133 except ValueError:
1121 print(' '.join([name.capitalize().ljust(width) 1147 print(' '.join([name.capitalize().ljust(width)
1122 for name, width in props])) 1148 for name, width in props]))
1123 1149
1124 # and the table data 1150 # and the table data
1125 for nodeid in cl.list(): 1151 for nodeid in cl.list():
1126 l = [] 1152 table_columns = []
1127 for name, width in props: 1153 for name, width in props:
1128 if name != 'id': 1154 if name != 'id':
1129 try: 1155 try:
1130 value = str(cl.get(nodeid, name)) 1156 value = str(cl.get(nodeid, name))
1131 except KeyError: 1157 except KeyError:
1134 # value for it 1160 # value for it
1135 value = '' 1161 value = ''
1136 else: 1162 else:
1137 value = str(nodeid) 1163 value = str(nodeid)
1138 f = '%%-%ds' % width 1164 f = '%%-%ds' % width
1139 l.append(f % value[:width]) 1165 table_columns.append(f % value[:width])
1140 print(' '.join(l)) 1166 print(' '.join(table_columns))
1141 return 0 1167 return 0
1142 1168
1143 def do_history(self, args): 1169 def do_history(self, args):
1144 ''"""Usage: history designator [skipquiet] 1170 ''"""Usage: history designator [skipquiet]
1145 Show the history entries of a designator. 1171 Show the history entries of a designator.
1257 dbclass.restore(nodeid) 1283 dbclass.restore(nodeid)
1258 except KeyError as e: 1284 except KeyError as e:
1259 raise UsageError(e.args[0]) 1285 raise UsageError(e.args[0])
1260 except IndexError: 1286 except IndexError:
1261 raise UsageError(_('no such %(classname)s node ' 1287 raise UsageError(_('no such %(classname)s node '
1262 '" % (nodeid)s"')%locals()) 1288 '" % (nodeid)s"') % locals())
1263 self.db_uncommitted = True 1289 self.db_uncommitted = True
1264 return 0 1290 return 0
1265 1291
1266 def do_export(self, args, export_files=True): 1292 def do_export(self, args, export_files=True):
1267 ''"""Usage: export [[-]class[,class]] export_dir 1293 ''"""Usage: export [[-]class[,class]] export_dir
1284 1310
1285 # get the list of classes to export 1311 # get the list of classes to export
1286 if len(args) == 2: 1312 if len(args) == 2:
1287 if args[0].startswith('-'): 1313 if args[0].startswith('-'):
1288 classes = [c for c in self.db.classes 1314 classes = [c for c in self.db.classes
1289 if c not in args[0][1:].split(',')] 1315 if c not in args[0][1:].split(',')]
1290 else: 1316 else:
1291 classes = args[0].split(',') 1317 classes = args[0].split(',')
1292 else: 1318 else:
1293 classes = self.db.classes 1319 classes = self.db.classes
1294 1320
1306 for classname in classes: 1332 for classname in classes:
1307 cl = self.get_class(classname) 1333 cl = self.get_class(classname)
1308 1334
1309 if not export_files and hasattr(cl, 'export_files'): 1335 if not export_files and hasattr(cl, 'export_files'):
1310 sys.stdout.write('Exporting %s WITHOUT the files\r\n' % 1336 sys.stdout.write('Exporting %s WITHOUT the files\r\n' %
1311 classname) 1337 classname)
1312 1338
1313 with open(os.path.join(dir, classname+'.csv'), 'w') as f: 1339 with open(os.path.join(dir, classname+'.csv'), 'w') as f:
1314 writer = csv.writer(f, colon_separated) 1340 writer = csv.writer(f, colon_separated)
1315 1341
1316 properties = cl.getprops()
1317 propnames = cl.export_propnames() 1342 propnames = cl.export_propnames()
1318 fields = propnames[:] 1343 fields = propnames[:]
1319 fields.append('is retired') 1344 fields.append('is retired')
1320 writer.writerow(fields) 1345 writer.writerow(fields)
1321 1346
1327 # _class.__retired__, _<class>._<keyname> 1352 # _class.__retired__, _<class>._<keyname>
1328 # on imports to rdbms. 1353 # on imports to rdbms.
1329 all_nodes = cl.getnodeids() 1354 all_nodes = cl.getnodeids()
1330 1355
1331 classkey = cl.getkey() 1356 classkey = cl.getkey()
1332 if classkey: # False sorts before True, so negate is_retired 1357 if classkey: # False sorts before True, so negate is_retired
1333 keysort = lambda i: (cl.get(i, classkey), 1358 keysort = lambda i: (cl.get(i, classkey),
1334 not cl.is_retired(i)) 1359 not cl.is_retired(i))
1335 all_nodes.sort(key=keysort) 1360 all_nodes.sort(key=keysort)
1336 # if there is no classkey no need to sort 1361 # if there is no classkey no need to sort
1337 1362
1338 for nodeid in all_nodes: 1363 for nodeid in all_nodes:
1339 if self.verbose: 1364 if self.verbose:
1340 sys.stdout.write('\rExporting %s - %s' % 1365 sys.stdout.write('\rExporting %s - %s' %
1341 (classname, nodeid)) 1366 (classname, nodeid))
1342 sys.stdout.flush() 1367 sys.stdout.flush()
1343 node = cl.getnode(nodeid) 1368 node = cl.getnode(nodeid)
1344 exp = cl.export_list(propnames, nodeid) 1369 exp = cl.export_list(propnames, nodeid)
1345 lensum = sum([len(repr_export(node[p])) for p in propnames]) 1370 lensum = sum([len(repr_export(node[p])) for
1371 p in propnames])
1346 # for a safe upper bound of field length we add 1372 # for a safe upper bound of field length we add
1347 # difference between CSV len and sum of all field lengths 1373 # difference between CSV len and sum of all field lengths
1348 d = sum([len(x) for x in exp]) - lensum 1374 d = sum([len(x) for x in exp]) - lensum
1349 if not d > 0: 1375 if not d > 0:
1350 raise AssertionError("Bad assertion d > 0") 1376 raise AssertionError("Bad assertion d > 0")
1357 cl.export_files(dir, nodeid) 1383 cl.export_files(dir, nodeid)
1358 1384
1359 # export the journals 1385 # export the journals
1360 with open(os.path.join(dir, classname+'-journals.csv'), 'w') as jf: 1386 with open(os.path.join(dir, classname+'-journals.csv'), 'w') as jf:
1361 if self.verbose: 1387 if self.verbose:
1362 sys.stdout.write("\nExporting Journal for %s\n" % classname) 1388 sys.stdout.write("\nExporting Journal for %s\n" %
1389 classname)
1363 sys.stdout.flush() 1390 sys.stdout.flush()
1364 journals = csv.writer(jf, colon_separated) 1391 journals = csv.writer(jf, colon_separated)
1365 for row in cl.export_journals(): 1392 for row in cl.export_journals():
1366 journals.writerow(row) 1393 journals.writerow(row)
1367 if max_len > self.db.config.CSV_FIELD_SIZE: 1394 if max_len > self.db.config.CSV_FIELD_SIZE:
1640 try: 1667 try:
1641 functions = self.commands.get(command) 1668 functions = self.commands.get(command)
1642 except KeyError: 1669 except KeyError:
1643 # not a valid command 1670 # not a valid command
1644 print(_('Unknown command "%(command)s" ("help commands" for a ' 1671 print(_('Unknown command "%(command)s" ("help commands" for a '
1645 'list)') % locals()) 1672 'list)') % locals())
1646 return 1 1673 return 1
1647 1674
1648 # check for multiple matches 1675 # check for multiple matches
1649 if len(functions) > 1: 1676 if len(functions) > 1:
1650 print(_('Multiple commands match "%(command)s": %(list)s') % \ 1677 print(_('Multiple commands match "%(command)s": %(list)s') %
1651 {'command': command, 1678 {'command': command,
1652 'list': ', '.join([i[0] for i in functions])}) 1679 'list': ', '.join([i[0] for i in functions])})
1653 return 1 1680 return 1
1654 command, function = functions[0] 1681 command, function = functions[0]
1655 1682
1682 print(_("Error: Couldn't open tracker: %(message)s") % locals()) 1709 print(_("Error: Couldn't open tracker: %(message)s") % locals())
1683 return 1 1710 return 1
1684 except NoConfigError as message: # noqa: F841 1711 except NoConfigError as message: # noqa: F841
1685 self.tracker_home = '' 1712 self.tracker_home = ''
1686 print(_("Error: Couldn't open tracker: %(message)s") % locals()) 1713 print(_("Error: Couldn't open tracker: %(message)s") % locals())
1714 return 1
1715 except ParsingOptionError as message: # message used via locals
1716 print("%(message)s" % locals())
1687 return 1 1717 return 1
1688 1718
1689 # only open the database once! 1719 # only open the database once!
1690 if not self.db: 1720 if not self.db:
1691 self.db = tracker.open(self.name) 1721 self.db = tracker.open(self.name)
1749 # handle command-line args 1779 # handle command-line args
1750 self.tracker_home = os.environ.get('TRACKER_HOME', '') 1780 self.tracker_home = os.environ.get('TRACKER_HOME', '')
1751 self.name = 'admin' 1781 self.name = 'admin'
1752 self.password = '' # unused 1782 self.password = '' # unused
1753 if 'ROUNDUP_LOGIN' in os.environ: 1783 if 'ROUNDUP_LOGIN' in os.environ:
1754 l = os.environ['ROUNDUP_LOGIN'].split(':') 1784 login_env = os.environ['ROUNDUP_LOGIN'].split(':')
1755 self.name = l[0] 1785 self.name = login_env[0]
1756 if len(l) > 1: 1786 if len(login_env) > 1:
1757 self.password = l[1] 1787 self.password = login_env[1]
1758 self.separator = None 1788 self.separator = None
1759 self.print_designator = 0 1789 self.print_designator = 0
1760 self.verbose = 0 1790 self.verbose = 0
1761 for opt, arg in opts: 1791 for opt, arg in opts:
1762 if opt == '-h': 1792 if opt == '-h':
1786 return 1 1816 return 1
1787 self.separator = ' ' 1817 self.separator = ' '
1788 elif opt == '-d': 1818 elif opt == '-d':
1789 self.print_designator = 1 1819 self.print_designator = 1
1790 elif opt == '-u': 1820 elif opt == '-u':
1791 l = arg.split(':') 1821 login_opt = arg.split(':')
1792 self.name = l[0] 1822 self.name = login_opt[0]
1793 if len(l) > 1: 1823 if len(login_opt) > 1:
1794 self.password = l[1] 1824 self.password = login_opt[1]
1795 1825
1796 # if no command - go interactive 1826 # if no command - go interactive
1797 # wrap in a try/finally so we always close off the db 1827 # wrap in a try/finally so we always close off the db
1798 ret = 0 1828 ret = 0
1799 try: 1829 try:

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