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

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