D299: pycompat: introduce a wrapper for __builtins__.{raw_,}input()

durin42 (Augie Fackler) phabricator at mercurial-scm.org
Wed Aug 9 14:25:04 UTC 2017


durin42 created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  In order to make this work, we have to wrap the io streams in a
  TextIOWrapper so that __builtins__.input() can do unicode IO on Python
  
  3. We can't just restore the original (unicode) sys.std* because we
  
  might be running a cmdserver, and if we blindly restore sys.* to the
  original values then we end up breaking the cmdserver. Sadly,
  TextIOWrapper tries to close the underlying stream during its __del__,
  so we have to make a sublcass to prevent that.
  
  If you see errors like:
  
  TypeError: a bytes-like object is required, not 'str'
  
  On an input() or print() call on Python 3, the substitution of
  sys.std* is probably the root cause.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D299

AFFECTED FILES
  hgext/hgk.py
  mercurial/pycompat.py
  mercurial/ui.py

CHANGE DETAILS

diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -1219,7 +1219,7 @@
         # prompt ' ' must exist; otherwise readline may delete entire line
         # - http://bugs.python.org/issue12833
         with self.timeblockedsection('stdio'):
-            line = raw_input(' ')
+            line = pycompat.bytesinput(r' ')
         sys.stdin = oldin
         sys.stdout = oldout
 
diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -69,6 +69,23 @@
     stdout = sys.stdout.buffer
     stderr = sys.stderr.buffer
 
+    class noclosetextio(io.TextIOWrapper):
+        def __del__(self):
+            """Override __del__ so it doesn't close the underlying stream."""
+
+    def bytesinput(*args, **kwargs):
+        origs = {}
+        try:
+            for stream in 'stdin stdout stderr'.split():
+                s = getattr(sys, stream)
+                origs[stream] = s
+                if not isinstance(s, io.TextIOBase):
+                    setattr(sys, stream, noclosetextio(s))
+            return bytestr(input(*args, **kwargs))
+        finally:
+            for stream, restore in origs.items():
+                setattr(sys, stream, restore)
+
     # Since Python 3 converts argv to wchar_t type by Py_DecodeLocale() on Unix,
     # we can use os.fsencode() to get back bytes argv.
     #
@@ -303,6 +320,7 @@
     stdin = sys.stdin
     stdout = sys.stdout
     stderr = sys.stderr
+    bytesinput = raw_input
     if getattr(sys, 'argv', None) is not None:
         sysargv = sys.argv
     sysplatform = sys.platform
diff --git a/hgext/hgk.py b/hgext/hgk.py
--- a/hgext/hgk.py
+++ b/hgext/hgk.py
@@ -48,6 +48,7 @@
     commands,
     obsolete,
     patch,
+    pycompat,
     registrar,
     scmutil,
 )
@@ -96,7 +97,7 @@
     while True:
         if opts['stdin']:
             try:
-                line = raw_input().split(' ')
+                line = pycompat.bytesinput().split(' ')
                 node1 = line[0]
                 if len(line) > 1:
                     node2 = line[1]
@@ -177,7 +178,7 @@
     prefix = ""
     if opts['stdin']:
         try:
-            (type, r) = raw_input().split(' ')
+            (type, r) = pycompat.bytesinput().split(' ')
             prefix = "    "
         except EOFError:
             return
@@ -195,7 +196,7 @@
         catcommit(ui, repo, n, prefix)
         if opts['stdin']:
             try:
-                (type, r) = raw_input().split(' ')
+                (type, r) = pycompat.bytesinput().split(' ')
             except EOFError:
                 break
         else:



To: durin42, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list