diff 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
line wrap: on
line diff
--- a/roundup/admin.py	Fri Aug 25 11:07:21 2017 -0400
+++ b/roundup/admin.py	Sat Aug 26 00:24:01 2017 -0400
@@ -19,6 +19,8 @@
 
 """Administration commands for maintaining Roundup trackers.
 """
+from __future__ import print_function
+
 __docformat__ = 'restructuredtext'
 
 import csv, getopt, getpass, os, re, shutil, sys, UserDict, operator
@@ -30,6 +32,12 @@
 from roundup.i18n import _
 from roundup.exceptions import UsageError
 
+# Polyglot code
+try:
+    my_input = raw_input
+except NameError:
+    my_input = input
+
 class CommandDict(UserDict.UserDict):
     """Simple dictionary that lets us do lookups using partial keys.
 
@@ -137,7 +145,7 @@
         """
         sys.stdout.write( _('Commands: '))
         commands = ['']
-        for command in self.commands.itervalues():
+        for command in self.commands.values():
             h = _(command.__doc__).split('\n')[0]
             commands.append(' '+h[7:])
         commands.sort()
@@ -149,27 +157,27 @@
     def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')):
         """ Produce an HTML command list.
         """
-        commands = sorted(self.commands.itervalues(),
+        commands = sorted(iter(self.commands.values()),
             operator.attrgetter('__name__'))
         for command in commands:
             h = _(command.__doc__).split('\n')
             name = command.__name__[3:]
             usage = h[0]
-            print """
+            print("""
 <tr><td valign=top><strong>%(name)s</strong></td>
     <td><tt>%(usage)s</tt><p>
-<pre>""" % locals()
+<pre>""" % locals())
             indent = indent_re.match(h[3])
             if indent: indent = len(indent.group(1))
             for line in h[3:]:
                 if indent:
-                    print line[indent:]
+                    print(line[indent:])
                 else:
-                    print line
-            print '</pre></td></tr>\n'
+                    print(line)
+            print('</pre></td></tr>\n')
 
     def help_all(self):
-        print _("""
+        print(_("""
 All commands (except help) require a tracker specifier. This is just
 the path to the roundup tracker you're working with. A roundup tracker
 is where roundup keeps the database and configuration file that defines
@@ -230,10 +238,10 @@
   "." means "right now"
 
 Command help:
-""")
-        for name, command in self.commands.items():
-            print _('%s:')%name
-            print '   ', _(command.__doc__)
+"""))
+        for name, command in list(self.commands.items()):
+            print(_('%s:')%name)
+            print('   ', _(command.__doc__))
 
     def do_help(self, args, nl_re=re.compile('[\r\n]'),
             indent_re=re.compile(r'^(\s+)\S+')):
@@ -260,20 +268,20 @@
         try:
             l = self.commands.get(topic)
         except KeyError:
-            print _('Sorry, no help for "%(topic)s"')%locals()
+            print(_('Sorry, no help for "%(topic)s"')%locals())
             return 1
 
         # display the help for each match, removing the docsring indent
         for name, help in l:
             lines = nl_re.split(_(help.__doc__))
-            print lines[0]
+            print(lines[0])
             indent = indent_re.match(lines[1])
             if indent: indent = len(indent.group(1))
             for line in lines[1:]:
                 if indent:
-                    print line[indent:]
+                    print(line[indent:])
                 else:
-                    print line
+                    print(line)
         return 0
 
     def listTemplates(self):
@@ -337,10 +345,10 @@
 
     def help_initopts(self):
         templates = self.listTemplates()
-        print _('Templates:'), ', '.join(templates)
+        print(_('Templates:'), ', '.join(templates))
         import roundup.backends
         backends = roundup.backends.list_backends()
-        print _('Back ends:'), ', '.join(backends)
+        print(_('Back ends:'), ', '.join(backends))
 
     def do_install(self, tracker_home, args):
         ''"""Usage: install [template [backend [key=val[,key=val]]]]
@@ -380,7 +388,7 @@
         if list(filter(os.path.exists, [config_ini_file,
                 os.path.join(tracker_home, 'config.py')])):
             if not self.force:
-                ok = raw_input(_(
+                ok = my_input(_(
 """WARNING: There appears to be a tracker in "%(tracker_home)s"!
 If you re-install it, you will lose all the data!
 Erase it? Y/N: """) % locals())
@@ -415,7 +423,7 @@
             try:
                 defns = dict([item.split("=") for item in args[3].split(",")])
             except:
-                print _('Error in configuration settings: "%s"') % args[3]
+                print(_('Error in configuration settings: "%s"') % args[3])
                 raise
         else:
             defns = {}
@@ -424,22 +432,22 @@
         # install!
         init.install(tracker_home, templates[template]['path'], settings=defns)
 
-        print _("""
+        print(_("""
 ---------------------------------------------------------------------------
  You should now edit the tracker configuration file:
-   %(config_file)s""") % {"config_file": config_ini_file}
+   %(config_file)s""") % {"config_file": config_ini_file})
 
         # find list of options that need manual adjustments
         # XXX config._get_unset_options() is marked as private
         #   (leading underscore).  make it public or don't care?
         need_set = CoreConfig(tracker_home)._get_unset_options()
         if need_set:
-            print _(" ... at a minimum, you must set following options:")
+            print(_(" ... at a minimum, you must set following options:"))
             for section in need_set:
-                print "   [%s]: %s" % (section, ", ".join(need_set[section]))
+                print("   [%s]: %s" % (section, ", ".join(need_set[section])))
 
         # note about schema modifications
-        print _("""
+        print(_("""
  If you wish to modify the database schema,
  you should also edit the schema file:
    %(database_config_file)s
@@ -453,7 +461,7 @@
 """) % {
     'database_config_file': os.path.join(tracker_home, 'schema.py'),
     'database_init_file': os.path.join(tracker_home, 'initial_data.py'),
-}
+})
         return 0
 
     def _get_choice(self, list_name, prompt, options, argument, default=None):
@@ -465,7 +473,7 @@
             return default
         sys.stdout.write('%s: %s\n' % (list_name, ', '.join(options)))
         while argument not in options:
-            argument = raw_input('%s [%s]: ' % (prompt, default))
+            argument = my_input('%s [%s]: ' % (prompt, default))
             if not argument:
                 return default
         return argument
@@ -522,7 +530,7 @@
         # is there already a database?
         if tracker.exists():
             if not self.force:
-                ok = raw_input(_(
+                ok = my_input(_(
 """WARNING: The database is already initialised!
 If you re-initialise it, you will lose all the data!
 Erase it? Y/N: """))
@@ -606,9 +614,9 @@
                         propclassname = self.db.getclass(property.classname).classname
                         id = cl.get(nodeid, propname)
                         for i in id:
-                            print propclassname + i
+                            print(propclassname + i)
                     else:
-                        print cl.get(nodeid, propname)
+                        print(cl.get(nodeid, propname))
             except IndexError:
                 raise UsageError(_('no such %(classname)s node '
                     '"%(nodeid)s"')%locals())
@@ -616,7 +624,7 @@
                 raise UsageError(_('no such %(classname)s property '
                     '"%(propname)s"')%locals())
         if self.separator:
-            print self.separator.join(l)
+            print(self.separator.join(l))
 
         return 0
 
@@ -665,7 +673,7 @@
             props = copy.copy(propset) # make a new copy for every designator
             cl = self.get_class(classname)
 
-            for key, value in props.items():
+            for key, value in list(props.items()):
                 try:
                     # You must reinitialize the props every time though.
                     # if props['nosy'] = '+admin' initally, it gets
@@ -705,7 +713,7 @@
         props = self.props_from_args(args[1:])
 
         # convert the user-input value to a value used for find()
-        for propname, value in props.iteritems():
+        for propname, value in props.items():
             if ',' in value:
                 values = value.split(',')
             else:
@@ -728,18 +736,18 @@
                     id = cl.find(**props)
                     for i in id:
                         designator.append(classname + i)
-                    print self.separator.join(designator)
+                    print(self.separator.join(designator))
                 else:
-                    print self.separator.join(cl.find(**props))
+                    print(self.separator.join(cl.find(**props)))
 
             else:
                 if self.print_designator:
                     id = cl.find(**props)
                     for i in id:
                         designator.append(classname + i)
-                    print designator
+                    print(designator)
                 else:
-                    print cl.find(**props)
+                    print(cl.find(**props))
         except KeyError:
             raise UsageError(_('%(classname)s has no property '
                 '"%(propname)s"')%locals())
@@ -795,7 +803,7 @@
             keys = sorted(cl.properties)
             for key in keys:
                 value = cl.get(nodeid, key)
-                print _('%(key)s: %(value)s')%locals()
+                print(_('%(key)s: %(value)s')%locals())
 
     def do_create(self, args):
         ''"""Usage: create classname property=value ...
@@ -830,11 +838,11 @@
                             'propname': key.capitalize()})
                         again = getpass.getpass(_('   %(propname)s (Again): ')%{
                             'propname': key.capitalize()})
-                        if value != again: print _('Sorry, try again...')
+                        if value != again: print(_('Sorry, try again...'))
                     if value:
                         props[key] = value
                 else:
-                    value = raw_input(_('%(propname)s (%(proptype)s): ')%{
+                    value = my_input(_('%(propname)s (%(proptype)s): ')%{
                         'propname': key.capitalize(), 'proptype': name})
                     if value:
                         props[key] = value
@@ -901,11 +909,11 @@
                     except KeyError:
                         raise UsageError(_('%(classname)s has no property '
                             '"%(propname)s"')%locals())
-                print self.separator.join(proplist)
+                print(self.separator.join(proplist))
             else:
                 # create a list of index id's since user didn't specify
                 # otherwise
-                print self.separator.join(cl.list())
+                print(self.separator.join(cl.list()))
         else:
             for nodeid in cl.list():
                 try:
@@ -913,7 +921,7 @@
                 except KeyError:
                     raise UsageError(_('%(classname)s has no property '
                         '"%(propname)s"')%locals())
-                print _('%(nodeid)4s: %(value)s')%locals()
+                print(_('%(nodeid)4s: %(value)s')%locals())
         return 0
 
     def do_table(self, args):
@@ -990,7 +998,7 @@
                props.append((spec, maxlen))
 
         # now display the heading
-        print ' '.join([name.capitalize().ljust(width) for name,width in props])
+        print(' '.join([name.capitalize().ljust(width) for name,width in props]))
 
         # and the table data
         for nodeid in cl.list():
@@ -1008,7 +1016,7 @@
                     value = str(nodeid)
                 f = '%%-%ds'%width
                 l.append(f%value[:width])
-            print ' '.join(l)
+            print(' '.join(l))
         return 0
 
     def do_history(self, args):
@@ -1038,8 +1046,8 @@
             skipquiet = True
 
         try:
-            print self.db.getclass(classname).history(nodeid,
-                                                      skipquiet=skipquiet)
+            print(self.db.getclass(classname).history(nodeid,
+                                                      skipquiet=skipquiet))
         except KeyError:
             raise UsageError(_('no such class "%(classname)s"')%locals())
         except IndexError:
@@ -1219,8 +1227,7 @@
                 journals.writerow(row)
             jf.close()
         if max_len > self.db.config.CSV_FIELD_SIZE:
-            print >> sys.stderr, \
-                "Warning: config csv_field_size should be at least %s"%max_len
+            print("Warning: config csv_field_size should be at least %s"%max_len, file=sys.stderr)
         return 0
 
     def do_exporttables(self, args):
@@ -1302,7 +1309,7 @@
                 maxid = max(maxid, int(nodeid))
 
             # (print to sys.stdout here to allow tests to squash it .. ugh)
-            print >> sys.stdout
+            print(file=sys.stdout)
 
             f.close()
 
@@ -1313,7 +1320,7 @@
             f.close()
 
             # (print to sys.stdout here to allow tests to squash it .. ugh)
-            print >> sys.stdout, 'setting', classname, maxid+1
+            print('setting', classname, maxid+1, file=sys.stdout)
 
             # set the id counter
             self.db.setid(classname, str(maxid+1))
@@ -1444,10 +1451,10 @@
         the habit.
         """
         if getattr(self.db, 'db_version_updated'):
-            print _('Tracker updated')
+            print(_('Tracker updated'))
             self.db_uncommitted = True
         else:
-            print _('No migration action required')
+            print(_('No migration action required'))
         return 0
 
     def run_command(self, args):
@@ -1473,21 +1480,21 @@
             functions = self.commands.get(command)
         except KeyError:
             # not a valid command
-            print _('Unknown command "%(command)s" ("help commands" for a '
-                'list)')%locals()
+            print(_('Unknown command "%(command)s" ("help commands" for a '
+                'list)')%locals())
             return 1
 
         # check for multiple matches
         if len(functions) > 1:
-            print _('Multiple commands match "%(command)s": %(list)s')%{'command':
-                command, 'list': ', '.join([i[0] for i in functions])}
+            print(_('Multiple commands match "%(command)s": %(list)s')%{'command':
+                command, 'list': ', '.join([i[0] for i in functions])})
             return 1
         command, function = functions[0]
 
         # make sure we have a tracker_home
         while not self.tracker_home:
             if not self.force:
-                self.tracker_home = raw_input(_('Enter tracker home: ')).strip()
+                self.tracker_home = my_input(_('Enter tracker home: ')).strip()
             else:
                 self.tracker_home = os.curdir
 
@@ -1496,13 +1503,13 @@
             try:
                 return self.do_initialise(self.tracker_home, args)
             except UsageError as message:
-                print _('Error: %(message)s')%locals()
+                print(_('Error: %(message)s')%locals())
                 return 1
         elif command == 'install':
             try:
                 return self.do_install(self.tracker_home, args)
             except UsageError as message:
-                print _('Error: %(message)s')%locals()
+                print(_('Error: %(message)s')%locals())
                 return 1
 
         # get the tracker
@@ -1510,11 +1517,11 @@
             tracker = roundup.instance.open(self.tracker_home)
         except ValueError as message:
             self.tracker_home = ''
-            print _("Error: Couldn't open tracker: %(message)s")%locals()
+            print(_("Error: Couldn't open tracker: %(message)s")%locals())
             return 1
         except NoConfigError as message:
             self.tracker_home = ''
-            print _("Error: Couldn't open tracker: %(message)s")%locals()
+            print(_("Error: Couldn't open tracker: %(message)s")%locals())
             return 1
 
         # only open the database once!
@@ -1528,9 +1535,9 @@
         try:
             ret = function(args[1:])
         except UsageError as message:
-            print _('Error: %(message)s')%locals()
-            print
-            print function.__doc__
+            print(_('Error: %(message)s')%locals())
+            print()
+            print(function.__doc__)
             ret = 1
         except:
             import traceback
@@ -1541,18 +1548,18 @@
     def interactive(self):
         """Run in an interactive mode
         """
-        print _('Roundup %s ready for input.\nType "help" for help.'
-            % roundup_version)
+        print(_('Roundup %s ready for input.\nType "help" for help.'
+            % roundup_version))
         try:
             import readline
         except ImportError:
-            print _('Note: command history and editing not available')
+            print(_('Note: command history and editing not available'))
 
         while 1:
             try:
-                command = raw_input(_('roundup> '))
+                command = my_input(_('roundup> '))
             except EOFError:
-                print _('exit...')
+                print(_('exit...'))
                 break
             if not command: continue
             try:
@@ -1565,7 +1572,7 @@
 
         # exit.. check for transactions
         if self.db and self.db_uncommitted:
-            commit = raw_input(_('There are unsaved changes. Commit them (y/N)? '))
+            commit = my_input(_('There are unsaved changes. Commit them (y/N)? '))
             if commit and commit[0].lower() == 'y':
                 self.db.commit()
         return 0
@@ -1594,7 +1601,7 @@
                 self.usage()
                 return 0
             elif opt == '-v':
-                print '%s (python %s)'%(roundup_version, sys.version.split()[0])
+                print('%s (python %s)'%(roundup_version, sys.version.split()[0]))
                 return 0
             elif opt == '-V':
                 self.verbose = 1

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