Mercurial > p > roundup > code
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: |
