Mercurial > p > roundup > code
comparison roundup/admin.py @ 5250:ac7fe3483206
Make admin.py 2/3-agnostic.
| author | Eric S. Raymond <esr@thyrsus.com> |
|---|---|
| date | Sat, 26 Aug 2017 00:24:01 -0400 |
| parents | 198b6e810c67 |
| children | 4cf48ff01e04 bed579f654ee |
comparison
equal
deleted
inserted
replaced
| 5249:bc250b4fb4c5 | 5250:ac7fe3483206 |
|---|---|
| 17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | 17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
| 18 # | 18 # |
| 19 | 19 |
| 20 """Administration commands for maintaining Roundup trackers. | 20 """Administration commands for maintaining Roundup trackers. |
| 21 """ | 21 """ |
| 22 from __future__ import print_function | |
| 23 | |
| 22 __docformat__ = 'restructuredtext' | 24 __docformat__ = 'restructuredtext' |
| 23 | 25 |
| 24 import csv, getopt, getpass, os, re, shutil, sys, UserDict, operator | 26 import csv, getopt, getpass, os, re, shutil, sys, UserDict, operator |
| 25 | 27 |
| 26 from roundup import date, hyperdb, roundupdb, init, password, token | 28 from roundup import date, hyperdb, roundupdb, init, password, token |
| 27 from roundup import __version__ as roundup_version | 29 from roundup import __version__ as roundup_version |
| 28 import roundup.instance | 30 import roundup.instance |
| 29 from roundup.configuration import CoreConfig, NoConfigError | 31 from roundup.configuration import CoreConfig, NoConfigError |
| 30 from roundup.i18n import _ | 32 from roundup.i18n import _ |
| 31 from roundup.exceptions import UsageError | 33 from roundup.exceptions import UsageError |
| 34 | |
| 35 # Polyglot code | |
| 36 try: | |
| 37 my_input = raw_input | |
| 38 except NameError: | |
| 39 my_input = input | |
| 32 | 40 |
| 33 class CommandDict(UserDict.UserDict): | 41 class CommandDict(UserDict.UserDict): |
| 34 """Simple dictionary that lets us do lookups using partial keys. | 42 """Simple dictionary that lets us do lookups using partial keys. |
| 35 | 43 |
| 36 Original code submitted by Engelbert Gruber. | 44 Original code submitted by Engelbert Gruber. |
| 135 def help_commands(self): | 143 def help_commands(self): |
| 136 """List the commands available with their help summary. | 144 """List the commands available with their help summary. |
| 137 """ | 145 """ |
| 138 sys.stdout.write( _('Commands: ')) | 146 sys.stdout.write( _('Commands: ')) |
| 139 commands = [''] | 147 commands = [''] |
| 140 for command in self.commands.itervalues(): | 148 for command in self.commands.values(): |
| 141 h = _(command.__doc__).split('\n')[0] | 149 h = _(command.__doc__).split('\n')[0] |
| 142 commands.append(' '+h[7:]) | 150 commands.append(' '+h[7:]) |
| 143 commands.sort() | 151 commands.sort() |
| 144 commands.append(_( | 152 commands.append(_( |
| 145 """Commands may be abbreviated as long as the abbreviation | 153 """Commands may be abbreviated as long as the abbreviation |
| 147 sys.stdout.write('\n'.join(commands) + '\n\n') | 155 sys.stdout.write('\n'.join(commands) + '\n\n') |
| 148 | 156 |
| 149 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')): | 157 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')): |
| 150 """ Produce an HTML command list. | 158 """ Produce an HTML command list. |
| 151 """ | 159 """ |
| 152 commands = sorted(self.commands.itervalues(), | 160 commands = sorted(iter(self.commands.values()), |
| 153 operator.attrgetter('__name__')) | 161 operator.attrgetter('__name__')) |
| 154 for command in commands: | 162 for command in commands: |
| 155 h = _(command.__doc__).split('\n') | 163 h = _(command.__doc__).split('\n') |
| 156 name = command.__name__[3:] | 164 name = command.__name__[3:] |
| 157 usage = h[0] | 165 usage = h[0] |
| 158 print """ | 166 print(""" |
| 159 <tr><td valign=top><strong>%(name)s</strong></td> | 167 <tr><td valign=top><strong>%(name)s</strong></td> |
| 160 <td><tt>%(usage)s</tt><p> | 168 <td><tt>%(usage)s</tt><p> |
| 161 <pre>""" % locals() | 169 <pre>""" % locals()) |
| 162 indent = indent_re.match(h[3]) | 170 indent = indent_re.match(h[3]) |
| 163 if indent: indent = len(indent.group(1)) | 171 if indent: indent = len(indent.group(1)) |
| 164 for line in h[3:]: | 172 for line in h[3:]: |
| 165 if indent: | 173 if indent: |
| 166 print line[indent:] | 174 print(line[indent:]) |
| 167 else: | 175 else: |
| 168 print line | 176 print(line) |
| 169 print '</pre></td></tr>\n' | 177 print('</pre></td></tr>\n') |
| 170 | 178 |
| 171 def help_all(self): | 179 def help_all(self): |
| 172 print _(""" | 180 print(_(""" |
| 173 All commands (except help) require a tracker specifier. This is just | 181 All commands (except help) require a tracker specifier. This is just |
| 174 the path to the roundup tracker you're working with. A roundup tracker | 182 the path to the roundup tracker you're working with. A roundup tracker |
| 175 is where roundup keeps the database and configuration file that defines | 183 is where roundup keeps the database and configuration file that defines |
| 176 an issue tracker. It may be thought of as the issue tracker's "home | 184 an issue tracker. It may be thought of as the issue tracker's "home |
| 177 directory". It may be specified in the environment variable TRACKER_HOME | 185 directory". It may be specified in the environment variable TRACKER_HOME |
| 228 "14:25" means <Date yyyy-mm-dd.19:25:00> | 236 "14:25" means <Date yyyy-mm-dd.19:25:00> |
| 229 "8:47:11" means <Date yyyy-mm-dd.13:47:11> | 237 "8:47:11" means <Date yyyy-mm-dd.13:47:11> |
| 230 "." means "right now" | 238 "." means "right now" |
| 231 | 239 |
| 232 Command help: | 240 Command help: |
| 233 """) | 241 """)) |
| 234 for name, command in self.commands.items(): | 242 for name, command in list(self.commands.items()): |
| 235 print _('%s:')%name | 243 print(_('%s:')%name) |
| 236 print ' ', _(command.__doc__) | 244 print(' ', _(command.__doc__)) |
| 237 | 245 |
| 238 def do_help(self, args, nl_re=re.compile('[\r\n]'), | 246 def do_help(self, args, nl_re=re.compile('[\r\n]'), |
| 239 indent_re=re.compile(r'^(\s+)\S+')): | 247 indent_re=re.compile(r'^(\s+)\S+')): |
| 240 ''"""Usage: help topic | 248 ''"""Usage: help topic |
| 241 Give help about topic. | 249 Give help about topic. |
| 258 | 266 |
| 259 # try command docstrings | 267 # try command docstrings |
| 260 try: | 268 try: |
| 261 l = self.commands.get(topic) | 269 l = self.commands.get(topic) |
| 262 except KeyError: | 270 except KeyError: |
| 263 print _('Sorry, no help for "%(topic)s"')%locals() | 271 print(_('Sorry, no help for "%(topic)s"')%locals()) |
| 264 return 1 | 272 return 1 |
| 265 | 273 |
| 266 # display the help for each match, removing the docsring indent | 274 # display the help for each match, removing the docsring indent |
| 267 for name, help in l: | 275 for name, help in l: |
| 268 lines = nl_re.split(_(help.__doc__)) | 276 lines = nl_re.split(_(help.__doc__)) |
| 269 print lines[0] | 277 print(lines[0]) |
| 270 indent = indent_re.match(lines[1]) | 278 indent = indent_re.match(lines[1]) |
| 271 if indent: indent = len(indent.group(1)) | 279 if indent: indent = len(indent.group(1)) |
| 272 for line in lines[1:]: | 280 for line in lines[1:]: |
| 273 if indent: | 281 if indent: |
| 274 print line[indent:] | 282 print(line[indent:]) |
| 275 else: | 283 else: |
| 276 print line | 284 print(line) |
| 277 return 0 | 285 return 0 |
| 278 | 286 |
| 279 def listTemplates(self): | 287 def listTemplates(self): |
| 280 """ List all the available templates. | 288 """ List all the available templates. |
| 281 | 289 |
| 335 | 343 |
| 336 return templates | 344 return templates |
| 337 | 345 |
| 338 def help_initopts(self): | 346 def help_initopts(self): |
| 339 templates = self.listTemplates() | 347 templates = self.listTemplates() |
| 340 print _('Templates:'), ', '.join(templates) | 348 print(_('Templates:'), ', '.join(templates)) |
| 341 import roundup.backends | 349 import roundup.backends |
| 342 backends = roundup.backends.list_backends() | 350 backends = roundup.backends.list_backends() |
| 343 print _('Back ends:'), ', '.join(backends) | 351 print(_('Back ends:'), ', '.join(backends)) |
| 344 | 352 |
| 345 def do_install(self, tracker_home, args): | 353 def do_install(self, tracker_home, args): |
| 346 ''"""Usage: install [template [backend [key=val[,key=val]]]] | 354 ''"""Usage: install [template [backend [key=val[,key=val]]]] |
| 347 Install a new Roundup tracker. | 355 Install a new Roundup tracker. |
| 348 | 356 |
| 378 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE) | 386 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE) |
| 379 # check for both old- and new-style configs | 387 # check for both old- and new-style configs |
| 380 if list(filter(os.path.exists, [config_ini_file, | 388 if list(filter(os.path.exists, [config_ini_file, |
| 381 os.path.join(tracker_home, 'config.py')])): | 389 os.path.join(tracker_home, 'config.py')])): |
| 382 if not self.force: | 390 if not self.force: |
| 383 ok = raw_input(_( | 391 ok = my_input(_( |
| 384 """WARNING: There appears to be a tracker in "%(tracker_home)s"! | 392 """WARNING: There appears to be a tracker in "%(tracker_home)s"! |
| 385 If you re-install it, you will lose all the data! | 393 If you re-install it, you will lose all the data! |
| 386 Erase it? Y/N: """) % locals()) | 394 Erase it? Y/N: """) % locals()) |
| 387 if ok.strip().lower() != 'y': | 395 if ok.strip().lower() != 'y': |
| 388 return 0 | 396 return 0 |
| 413 # Process configuration file definitions | 421 # Process configuration file definitions |
| 414 if len(args) > 3: | 422 if len(args) > 3: |
| 415 try: | 423 try: |
| 416 defns = dict([item.split("=") for item in args[3].split(",")]) | 424 defns = dict([item.split("=") for item in args[3].split(",")]) |
| 417 except: | 425 except: |
| 418 print _('Error in configuration settings: "%s"') % args[3] | 426 print(_('Error in configuration settings: "%s"') % args[3]) |
| 419 raise | 427 raise |
| 420 else: | 428 else: |
| 421 defns = {} | 429 defns = {} |
| 422 | 430 |
| 423 defns['rdbms_backend'] = backend | 431 defns['rdbms_backend'] = backend |
| 424 # install! | 432 # install! |
| 425 init.install(tracker_home, templates[template]['path'], settings=defns) | 433 init.install(tracker_home, templates[template]['path'], settings=defns) |
| 426 | 434 |
| 427 print _(""" | 435 print(_(""" |
| 428 --------------------------------------------------------------------------- | 436 --------------------------------------------------------------------------- |
| 429 You should now edit the tracker configuration file: | 437 You should now edit the tracker configuration file: |
| 430 %(config_file)s""") % {"config_file": config_ini_file} | 438 %(config_file)s""") % {"config_file": config_ini_file}) |
| 431 | 439 |
| 432 # find list of options that need manual adjustments | 440 # find list of options that need manual adjustments |
| 433 # XXX config._get_unset_options() is marked as private | 441 # XXX config._get_unset_options() is marked as private |
| 434 # (leading underscore). make it public or don't care? | 442 # (leading underscore). make it public or don't care? |
| 435 need_set = CoreConfig(tracker_home)._get_unset_options() | 443 need_set = CoreConfig(tracker_home)._get_unset_options() |
| 436 if need_set: | 444 if need_set: |
| 437 print _(" ... at a minimum, you must set following options:") | 445 print(_(" ... at a minimum, you must set following options:")) |
| 438 for section in need_set: | 446 for section in need_set: |
| 439 print " [%s]: %s" % (section, ", ".join(need_set[section])) | 447 print(" [%s]: %s" % (section, ", ".join(need_set[section]))) |
| 440 | 448 |
| 441 # note about schema modifications | 449 # note about schema modifications |
| 442 print _(""" | 450 print(_(""" |
| 443 If you wish to modify the database schema, | 451 If you wish to modify the database schema, |
| 444 you should also edit the schema file: | 452 you should also edit the schema file: |
| 445 %(database_config_file)s | 453 %(database_config_file)s |
| 446 You may also change the database initialisation file: | 454 You may also change the database initialisation file: |
| 447 %(database_init_file)s | 455 %(database_init_file)s |
| 451 the above steps. | 459 the above steps. |
| 452 --------------------------------------------------------------------------- | 460 --------------------------------------------------------------------------- |
| 453 """) % { | 461 """) % { |
| 454 'database_config_file': os.path.join(tracker_home, 'schema.py'), | 462 'database_config_file': os.path.join(tracker_home, 'schema.py'), |
| 455 'database_init_file': os.path.join(tracker_home, 'initial_data.py'), | 463 'database_init_file': os.path.join(tracker_home, 'initial_data.py'), |
| 456 } | 464 }) |
| 457 return 0 | 465 return 0 |
| 458 | 466 |
| 459 def _get_choice(self, list_name, prompt, options, argument, default=None): | 467 def _get_choice(self, list_name, prompt, options, argument, default=None): |
| 460 if default is None: | 468 if default is None: |
| 461 default = options[0] # just pick the first one | 469 default = options[0] # just pick the first one |
| 463 return argument | 471 return argument |
| 464 if self.force: | 472 if self.force: |
| 465 return default | 473 return default |
| 466 sys.stdout.write('%s: %s\n' % (list_name, ', '.join(options))) | 474 sys.stdout.write('%s: %s\n' % (list_name, ', '.join(options))) |
| 467 while argument not in options: | 475 while argument not in options: |
| 468 argument = raw_input('%s [%s]: ' % (prompt, default)) | 476 argument = my_input('%s [%s]: ' % (prompt, default)) |
| 469 if not argument: | 477 if not argument: |
| 470 return default | 478 return default |
| 471 return argument | 479 return argument |
| 472 | 480 |
| 473 def do_genconfig(self, args, update=False): | 481 def do_genconfig(self, args, update=False): |
| 520 raise UsageError(_('Instance has not been installed')%locals()) | 528 raise UsageError(_('Instance has not been installed')%locals()) |
| 521 | 529 |
| 522 # is there already a database? | 530 # is there already a database? |
| 523 if tracker.exists(): | 531 if tracker.exists(): |
| 524 if not self.force: | 532 if not self.force: |
| 525 ok = raw_input(_( | 533 ok = my_input(_( |
| 526 """WARNING: The database is already initialised! | 534 """WARNING: The database is already initialised! |
| 527 If you re-initialise it, you will lose all the data! | 535 If you re-initialise it, you will lose all the data! |
| 528 Erase it? Y/N: """)) | 536 Erase it? Y/N: """)) |
| 529 if ok.strip().lower() != 'y': | 537 if ok.strip().lower() != 'y': |
| 530 return 0 | 538 return 0 |
| 604 ' Multilink or Link so -d flag does not ' | 612 ' Multilink or Link so -d flag does not ' |
| 605 'apply.')%propname) | 613 'apply.')%propname) |
| 606 propclassname = self.db.getclass(property.classname).classname | 614 propclassname = self.db.getclass(property.classname).classname |
| 607 id = cl.get(nodeid, propname) | 615 id = cl.get(nodeid, propname) |
| 608 for i in id: | 616 for i in id: |
| 609 print propclassname + i | 617 print(propclassname + i) |
| 610 else: | 618 else: |
| 611 print cl.get(nodeid, propname) | 619 print(cl.get(nodeid, propname)) |
| 612 except IndexError: | 620 except IndexError: |
| 613 raise UsageError(_('no such %(classname)s node ' | 621 raise UsageError(_('no such %(classname)s node ' |
| 614 '"%(nodeid)s"')%locals()) | 622 '"%(nodeid)s"')%locals()) |
| 615 except KeyError: | 623 except KeyError: |
| 616 raise UsageError(_('no such %(classname)s property ' | 624 raise UsageError(_('no such %(classname)s property ' |
| 617 '"%(propname)s"')%locals()) | 625 '"%(propname)s"')%locals()) |
| 618 if self.separator: | 626 if self.separator: |
| 619 print self.separator.join(l) | 627 print(self.separator.join(l)) |
| 620 | 628 |
| 621 return 0 | 629 return 0 |
| 622 | 630 |
| 623 | 631 |
| 624 def do_set(self, args): | 632 def do_set(self, args): |
| 663 # now do the set for all the nodes | 671 # now do the set for all the nodes |
| 664 for classname, itemid in designators: | 672 for classname, itemid in designators: |
| 665 props = copy.copy(propset) # make a new copy for every designator | 673 props = copy.copy(propset) # make a new copy for every designator |
| 666 cl = self.get_class(classname) | 674 cl = self.get_class(classname) |
| 667 | 675 |
| 668 for key, value in props.items(): | 676 for key, value in list(props.items()): |
| 669 try: | 677 try: |
| 670 # You must reinitialize the props every time though. | 678 # You must reinitialize the props every time though. |
| 671 # if props['nosy'] = '+admin' initally, it gets | 679 # if props['nosy'] = '+admin' initally, it gets |
| 672 # set to 'demo,admin' (assuming it was set to demo | 680 # set to 'demo,admin' (assuming it was set to demo |
| 673 # in the db) after rawToHyperdb returns. | 681 # in the db) after rawToHyperdb returns. |
| 703 | 711 |
| 704 # handle the propname=value argument | 712 # handle the propname=value argument |
| 705 props = self.props_from_args(args[1:]) | 713 props = self.props_from_args(args[1:]) |
| 706 | 714 |
| 707 # convert the user-input value to a value used for find() | 715 # convert the user-input value to a value used for find() |
| 708 for propname, value in props.iteritems(): | 716 for propname, value in props.items(): |
| 709 if ',' in value: | 717 if ',' in value: |
| 710 values = value.split(',') | 718 values = value.split(',') |
| 711 else: | 719 else: |
| 712 values = [value] | 720 values = [value] |
| 713 d = props[propname] = {} | 721 d = props[propname] = {} |
| 726 if self.separator: | 734 if self.separator: |
| 727 if self.print_designator: | 735 if self.print_designator: |
| 728 id = cl.find(**props) | 736 id = cl.find(**props) |
| 729 for i in id: | 737 for i in id: |
| 730 designator.append(classname + i) | 738 designator.append(classname + i) |
| 731 print self.separator.join(designator) | 739 print(self.separator.join(designator)) |
| 732 else: | 740 else: |
| 733 print self.separator.join(cl.find(**props)) | 741 print(self.separator.join(cl.find(**props))) |
| 734 | 742 |
| 735 else: | 743 else: |
| 736 if self.print_designator: | 744 if self.print_designator: |
| 737 id = cl.find(**props) | 745 id = cl.find(**props) |
| 738 for i in id: | 746 for i in id: |
| 739 designator.append(classname + i) | 747 designator.append(classname + i) |
| 740 print designator | 748 print(designator) |
| 741 else: | 749 else: |
| 742 print cl.find(**props) | 750 print(cl.find(**props)) |
| 743 except KeyError: | 751 except KeyError: |
| 744 raise UsageError(_('%(classname)s has no property ' | 752 raise UsageError(_('%(classname)s has no property ' |
| 745 '"%(propname)s"')%locals()) | 753 '"%(propname)s"')%locals()) |
| 746 except (ValueError, TypeError) as message: | 754 except (ValueError, TypeError) as message: |
| 747 raise UsageError(message) | 755 raise UsageError(message) |
| 793 | 801 |
| 794 # display the values | 802 # display the values |
| 795 keys = sorted(cl.properties) | 803 keys = sorted(cl.properties) |
| 796 for key in keys: | 804 for key in keys: |
| 797 value = cl.get(nodeid, key) | 805 value = cl.get(nodeid, key) |
| 798 print _('%(key)s: %(value)s')%locals() | 806 print(_('%(key)s: %(value)s')%locals()) |
| 799 | 807 |
| 800 def do_create(self, args): | 808 def do_create(self, args): |
| 801 ''"""Usage: create classname property=value ... | 809 ''"""Usage: create classname property=value ... |
| 802 Create a new entry of a given class. | 810 Create a new entry of a given class. |
| 803 | 811 |
| 828 while value != again: | 836 while value != again: |
| 829 value = getpass.getpass(_('%(propname)s (Password): ')%{ | 837 value = getpass.getpass(_('%(propname)s (Password): ')%{ |
| 830 'propname': key.capitalize()}) | 838 'propname': key.capitalize()}) |
| 831 again = getpass.getpass(_(' %(propname)s (Again): ')%{ | 839 again = getpass.getpass(_(' %(propname)s (Again): ')%{ |
| 832 'propname': key.capitalize()}) | 840 'propname': key.capitalize()}) |
| 833 if value != again: print _('Sorry, try again...') | 841 if value != again: print(_('Sorry, try again...')) |
| 834 if value: | 842 if value: |
| 835 props[key] = value | 843 props[key] = value |
| 836 else: | 844 else: |
| 837 value = raw_input(_('%(propname)s (%(proptype)s): ')%{ | 845 value = my_input(_('%(propname)s (%(proptype)s): ')%{ |
| 838 'propname': key.capitalize(), 'proptype': name}) | 846 'propname': key.capitalize(), 'proptype': name}) |
| 839 if value: | 847 if value: |
| 840 props[key] = value | 848 props[key] = value |
| 841 else: | 849 else: |
| 842 props = self.props_from_args(args[1:]) | 850 props = self.props_from_args(args[1:]) |
| 899 try: | 907 try: |
| 900 proplist.append(cl.get(nodeid, propname)) | 908 proplist.append(cl.get(nodeid, propname)) |
| 901 except KeyError: | 909 except KeyError: |
| 902 raise UsageError(_('%(classname)s has no property ' | 910 raise UsageError(_('%(classname)s has no property ' |
| 903 '"%(propname)s"')%locals()) | 911 '"%(propname)s"')%locals()) |
| 904 print self.separator.join(proplist) | 912 print(self.separator.join(proplist)) |
| 905 else: | 913 else: |
| 906 # create a list of index id's since user didn't specify | 914 # create a list of index id's since user didn't specify |
| 907 # otherwise | 915 # otherwise |
| 908 print self.separator.join(cl.list()) | 916 print(self.separator.join(cl.list())) |
| 909 else: | 917 else: |
| 910 for nodeid in cl.list(): | 918 for nodeid in cl.list(): |
| 911 try: | 919 try: |
| 912 value = cl.get(nodeid, propname) | 920 value = cl.get(nodeid, propname) |
| 913 except KeyError: | 921 except KeyError: |
| 914 raise UsageError(_('%(classname)s has no property ' | 922 raise UsageError(_('%(classname)s has no property ' |
| 915 '"%(propname)s"')%locals()) | 923 '"%(propname)s"')%locals()) |
| 916 print _('%(nodeid)4s: %(value)s')%locals() | 924 print(_('%(nodeid)4s: %(value)s')%locals()) |
| 917 return 0 | 925 return 0 |
| 918 | 926 |
| 919 def do_table(self, args): | 927 def do_table(self, args): |
| 920 ''"""Usage: table classname [property[,property]*] | 928 ''"""Usage: table classname [property[,property]*] |
| 921 List the instances of a class in tabular form. | 929 List the instances of a class in tabular form. |
| 988 if curlen > maxlen: | 996 if curlen > maxlen: |
| 989 maxlen = curlen | 997 maxlen = curlen |
| 990 props.append((spec, maxlen)) | 998 props.append((spec, maxlen)) |
| 991 | 999 |
| 992 # now display the heading | 1000 # now display the heading |
| 993 print ' '.join([name.capitalize().ljust(width) for name,width in props]) | 1001 print(' '.join([name.capitalize().ljust(width) for name,width in props])) |
| 994 | 1002 |
| 995 # and the table data | 1003 # and the table data |
| 996 for nodeid in cl.list(): | 1004 for nodeid in cl.list(): |
| 997 l = [] | 1005 l = [] |
| 998 for name, width in props: | 1006 for name, width in props: |
| 1006 value = '' | 1014 value = '' |
| 1007 else: | 1015 else: |
| 1008 value = str(nodeid) | 1016 value = str(nodeid) |
| 1009 f = '%%-%ds'%width | 1017 f = '%%-%ds'%width |
| 1010 l.append(f%value[:width]) | 1018 l.append(f%value[:width]) |
| 1011 print ' '.join(l) | 1019 print(' '.join(l)) |
| 1012 return 0 | 1020 return 0 |
| 1013 | 1021 |
| 1014 def do_history(self, args): | 1022 def do_history(self, args): |
| 1015 ''"""Usage: history designator [skipquiet] | 1023 ''"""Usage: history designator [skipquiet] |
| 1016 Show the history entries of a designator. | 1024 Show the history entries of a designator. |
| 1036 if args[1] != 'skipquiet': | 1044 if args[1] != 'skipquiet': |
| 1037 raise UsageError("Second argument is not skipquiet") | 1045 raise UsageError("Second argument is not skipquiet") |
| 1038 skipquiet = True | 1046 skipquiet = True |
| 1039 | 1047 |
| 1040 try: | 1048 try: |
| 1041 print self.db.getclass(classname).history(nodeid, | 1049 print(self.db.getclass(classname).history(nodeid, |
| 1042 skipquiet=skipquiet) | 1050 skipquiet=skipquiet)) |
| 1043 except KeyError: | 1051 except KeyError: |
| 1044 raise UsageError(_('no such class "%(classname)s"')%locals()) | 1052 raise UsageError(_('no such class "%(classname)s"')%locals()) |
| 1045 except IndexError: | 1053 except IndexError: |
| 1046 raise UsageError(_('no such %(classname)s node ' | 1054 raise UsageError(_('no such %(classname)s node ' |
| 1047 '"%(nodeid)s"')%locals()) | 1055 '"%(nodeid)s"')%locals()) |
| 1217 journals = csv.writer(jf, colon_separated) | 1225 journals = csv.writer(jf, colon_separated) |
| 1218 for row in cl.export_journals(): | 1226 for row in cl.export_journals(): |
| 1219 journals.writerow(row) | 1227 journals.writerow(row) |
| 1220 jf.close() | 1228 jf.close() |
| 1221 if max_len > self.db.config.CSV_FIELD_SIZE: | 1229 if max_len > self.db.config.CSV_FIELD_SIZE: |
| 1222 print >> sys.stderr, \ | 1230 print("Warning: config csv_field_size should be at least %s"%max_len, file=sys.stderr) |
| 1223 "Warning: config csv_field_size should be at least %s"%max_len | |
| 1224 return 0 | 1231 return 0 |
| 1225 | 1232 |
| 1226 def do_exporttables(self, args): | 1233 def do_exporttables(self, args): |
| 1227 ''"""Usage: exporttables [[-]class[,class]] export_dir | 1234 ''"""Usage: exporttables [[-]class[,class]] export_dir |
| 1228 Export the database to colon-separated-value files, excluding the | 1235 Export the database to colon-separated-value files, excluding the |
| 1300 if hasattr(cl, 'import_files'): | 1307 if hasattr(cl, 'import_files'): |
| 1301 cl.import_files(dir, nodeid) | 1308 cl.import_files(dir, nodeid) |
| 1302 maxid = max(maxid, int(nodeid)) | 1309 maxid = max(maxid, int(nodeid)) |
| 1303 | 1310 |
| 1304 # (print to sys.stdout here to allow tests to squash it .. ugh) | 1311 # (print to sys.stdout here to allow tests to squash it .. ugh) |
| 1305 print >> sys.stdout | 1312 print(file=sys.stdout) |
| 1306 | 1313 |
| 1307 f.close() | 1314 f.close() |
| 1308 | 1315 |
| 1309 # import the journals | 1316 # import the journals |
| 1310 f = open(os.path.join(args[0], classname + '-journals.csv'), 'r') | 1317 f = open(os.path.join(args[0], classname + '-journals.csv'), 'r') |
| 1311 reader = csv.reader(f, colon_separated) | 1318 reader = csv.reader(f, colon_separated) |
| 1312 cl.import_journals(reader) | 1319 cl.import_journals(reader) |
| 1313 f.close() | 1320 f.close() |
| 1314 | 1321 |
| 1315 # (print to sys.stdout here to allow tests to squash it .. ugh) | 1322 # (print to sys.stdout here to allow tests to squash it .. ugh) |
| 1316 print >> sys.stdout, 'setting', classname, maxid+1 | 1323 print('setting', classname, maxid+1, file=sys.stdout) |
| 1317 | 1324 |
| 1318 # set the id counter | 1325 # set the id counter |
| 1319 self.db.setid(classname, str(maxid+1)) | 1326 self.db.setid(classname, str(maxid+1)) |
| 1320 | 1327 |
| 1321 self.db_uncommitted = True | 1328 self.db_uncommitted = True |
| 1442 | 1449 |
| 1443 It's safe to run this even if it's not required, so just get into | 1450 It's safe to run this even if it's not required, so just get into |
| 1444 the habit. | 1451 the habit. |
| 1445 """ | 1452 """ |
| 1446 if getattr(self.db, 'db_version_updated'): | 1453 if getattr(self.db, 'db_version_updated'): |
| 1447 print _('Tracker updated') | 1454 print(_('Tracker updated')) |
| 1448 self.db_uncommitted = True | 1455 self.db_uncommitted = True |
| 1449 else: | 1456 else: |
| 1450 print _('No migration action required') | 1457 print(_('No migration action required')) |
| 1451 return 0 | 1458 return 0 |
| 1452 | 1459 |
| 1453 def run_command(self, args): | 1460 def run_command(self, args): |
| 1454 """Run a single command | 1461 """Run a single command |
| 1455 """ | 1462 """ |
| 1471 # figure what the command is | 1478 # figure what the command is |
| 1472 try: | 1479 try: |
| 1473 functions = self.commands.get(command) | 1480 functions = self.commands.get(command) |
| 1474 except KeyError: | 1481 except KeyError: |
| 1475 # not a valid command | 1482 # not a valid command |
| 1476 print _('Unknown command "%(command)s" ("help commands" for a ' | 1483 print(_('Unknown command "%(command)s" ("help commands" for a ' |
| 1477 'list)')%locals() | 1484 'list)')%locals()) |
| 1478 return 1 | 1485 return 1 |
| 1479 | 1486 |
| 1480 # check for multiple matches | 1487 # check for multiple matches |
| 1481 if len(functions) > 1: | 1488 if len(functions) > 1: |
| 1482 print _('Multiple commands match "%(command)s": %(list)s')%{'command': | 1489 print(_('Multiple commands match "%(command)s": %(list)s')%{'command': |
| 1483 command, 'list': ', '.join([i[0] for i in functions])} | 1490 command, 'list': ', '.join([i[0] for i in functions])}) |
| 1484 return 1 | 1491 return 1 |
| 1485 command, function = functions[0] | 1492 command, function = functions[0] |
| 1486 | 1493 |
| 1487 # make sure we have a tracker_home | 1494 # make sure we have a tracker_home |
| 1488 while not self.tracker_home: | 1495 while not self.tracker_home: |
| 1489 if not self.force: | 1496 if not self.force: |
| 1490 self.tracker_home = raw_input(_('Enter tracker home: ')).strip() | 1497 self.tracker_home = my_input(_('Enter tracker home: ')).strip() |
| 1491 else: | 1498 else: |
| 1492 self.tracker_home = os.curdir | 1499 self.tracker_home = os.curdir |
| 1493 | 1500 |
| 1494 # before we open the db, we may be doing an install or init | 1501 # before we open the db, we may be doing an install or init |
| 1495 if command == 'initialise': | 1502 if command == 'initialise': |
| 1496 try: | 1503 try: |
| 1497 return self.do_initialise(self.tracker_home, args) | 1504 return self.do_initialise(self.tracker_home, args) |
| 1498 except UsageError as message: | 1505 except UsageError as message: |
| 1499 print _('Error: %(message)s')%locals() | 1506 print(_('Error: %(message)s')%locals()) |
| 1500 return 1 | 1507 return 1 |
| 1501 elif command == 'install': | 1508 elif command == 'install': |
| 1502 try: | 1509 try: |
| 1503 return self.do_install(self.tracker_home, args) | 1510 return self.do_install(self.tracker_home, args) |
| 1504 except UsageError as message: | 1511 except UsageError as message: |
| 1505 print _('Error: %(message)s')%locals() | 1512 print(_('Error: %(message)s')%locals()) |
| 1506 return 1 | 1513 return 1 |
| 1507 | 1514 |
| 1508 # get the tracker | 1515 # get the tracker |
| 1509 try: | 1516 try: |
| 1510 tracker = roundup.instance.open(self.tracker_home) | 1517 tracker = roundup.instance.open(self.tracker_home) |
| 1511 except ValueError as message: | 1518 except ValueError as message: |
| 1512 self.tracker_home = '' | 1519 self.tracker_home = '' |
| 1513 print _("Error: Couldn't open tracker: %(message)s")%locals() | 1520 print(_("Error: Couldn't open tracker: %(message)s")%locals()) |
| 1514 return 1 | 1521 return 1 |
| 1515 except NoConfigError as message: | 1522 except NoConfigError as message: |
| 1516 self.tracker_home = '' | 1523 self.tracker_home = '' |
| 1517 print _("Error: Couldn't open tracker: %(message)s")%locals() | 1524 print(_("Error: Couldn't open tracker: %(message)s")%locals()) |
| 1518 return 1 | 1525 return 1 |
| 1519 | 1526 |
| 1520 # only open the database once! | 1527 # only open the database once! |
| 1521 if not self.db: | 1528 if not self.db: |
| 1522 self.db = tracker.open('admin') | 1529 self.db = tracker.open('admin') |
| 1526 # do the command | 1533 # do the command |
| 1527 ret = 0 | 1534 ret = 0 |
| 1528 try: | 1535 try: |
| 1529 ret = function(args[1:]) | 1536 ret = function(args[1:]) |
| 1530 except UsageError as message: | 1537 except UsageError as message: |
| 1531 print _('Error: %(message)s')%locals() | 1538 print(_('Error: %(message)s')%locals()) |
| 1532 print | 1539 print() |
| 1533 print function.__doc__ | 1540 print(function.__doc__) |
| 1534 ret = 1 | 1541 ret = 1 |
| 1535 except: | 1542 except: |
| 1536 import traceback | 1543 import traceback |
| 1537 traceback.print_exc() | 1544 traceback.print_exc() |
| 1538 ret = 1 | 1545 ret = 1 |
| 1539 return ret | 1546 return ret |
| 1540 | 1547 |
| 1541 def interactive(self): | 1548 def interactive(self): |
| 1542 """Run in an interactive mode | 1549 """Run in an interactive mode |
| 1543 """ | 1550 """ |
| 1544 print _('Roundup %s ready for input.\nType "help" for help.' | 1551 print(_('Roundup %s ready for input.\nType "help" for help.' |
| 1545 % roundup_version) | 1552 % roundup_version)) |
| 1546 try: | 1553 try: |
| 1547 import readline | 1554 import readline |
| 1548 except ImportError: | 1555 except ImportError: |
| 1549 print _('Note: command history and editing not available') | 1556 print(_('Note: command history and editing not available')) |
| 1550 | 1557 |
| 1551 while 1: | 1558 while 1: |
| 1552 try: | 1559 try: |
| 1553 command = raw_input(_('roundup> ')) | 1560 command = my_input(_('roundup> ')) |
| 1554 except EOFError: | 1561 except EOFError: |
| 1555 print _('exit...') | 1562 print(_('exit...')) |
| 1556 break | 1563 break |
| 1557 if not command: continue | 1564 if not command: continue |
| 1558 try: | 1565 try: |
| 1559 args = token.token_split(command) | 1566 args = token.token_split(command) |
| 1560 except ValueError: | 1567 except ValueError: |
| 1563 if args[0] in ('quit', 'exit'): break | 1570 if args[0] in ('quit', 'exit'): break |
| 1564 self.run_command(args) | 1571 self.run_command(args) |
| 1565 | 1572 |
| 1566 # exit.. check for transactions | 1573 # exit.. check for transactions |
| 1567 if self.db and self.db_uncommitted: | 1574 if self.db and self.db_uncommitted: |
| 1568 commit = raw_input(_('There are unsaved changes. Commit them (y/N)? ')) | 1575 commit = my_input(_('There are unsaved changes. Commit them (y/N)? ')) |
| 1569 if commit and commit[0].lower() == 'y': | 1576 if commit and commit[0].lower() == 'y': |
| 1570 self.db.commit() | 1577 self.db.commit() |
| 1571 return 0 | 1578 return 0 |
| 1572 | 1579 |
| 1573 def main(self): | 1580 def main(self): |
| 1592 for opt, arg in opts: | 1599 for opt, arg in opts: |
| 1593 if opt == '-h': | 1600 if opt == '-h': |
| 1594 self.usage() | 1601 self.usage() |
| 1595 return 0 | 1602 return 0 |
| 1596 elif opt == '-v': | 1603 elif opt == '-v': |
| 1597 print '%s (python %s)'%(roundup_version, sys.version.split()[0]) | 1604 print('%s (python %s)'%(roundup_version, sys.version.split()[0])) |
| 1598 return 0 | 1605 return 0 |
| 1599 elif opt == '-V': | 1606 elif opt == '-V': |
| 1600 self.verbose = 1 | 1607 self.verbose = 1 |
| 1601 elif opt == '-i': | 1608 elif opt == '-i': |
| 1602 self.tracker_home = arg | 1609 self.tracker_home = arg |
