From cec5c0d7768aceb04bf9df457351bc00141f48b6 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 11 Apr 2013 15:21:06 -0700 Subject: [PATCH 1/3] Few changes to support running on python3 --- q.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/q.py b/q.py index c81a371..abe5b1e 100644 --- a/q.py +++ b/q.py @@ -70,11 +70,11 @@ class FileWriter(object): def __init__(self, path): self.path = path - self.open = file + self.open = open # App Engine's dev_appserver patches 'open' to simulate security # restrictions in production; we circumvent this to write output. - if file.__name__ == 'FakeFile': # dev_appserver's patched 'file' - self.open = file.__bases__[0] # the original built-in 'file' + if open.__name__ == 'FakeFile': # dev_appserver's patched 'file' + self.open = open.__bases__[0] # the original built-in 'file' def write(self, mode, content): try: @@ -130,7 +130,7 @@ def newline(self): def add(self, items, sep='', wrap=True): """Adds a list of strings that are to be printed on one line.""" - items = map(str, items) + items = list(map(str, items)) size = sum([len(x) for x in items if not x.startswith('\x1b')]) if (wrap and self.column > self.indent and self.column + len(sep) + size > self.width): @@ -155,17 +155,29 @@ def unindent(self, lines): indent = min(len(self.re.match(r'^ *', line).group()) for line in lines) return [line[indent:].rstrip() for line in lines] + def _isbasestring(self, value): + if self.sys.version_info >= (3,): + return isinstance(value, (str, bytes)) + else: + return isinstance(value, basestring) + + def _istext(self, value): + if self.sys.version_info >= (3,): + return isinstance(value, str) + else: + return isinstance(value, unicode) + def safe_repr(self, value): # TODO: Use colour to distinguish '...' elision from actual '...' chars. # TODO: Show a nicer repr for SRE.Match objects. # TODO: Show a nicer repr for big multiline strings. result = self.TEXT_REPR.repr(value) - if isinstance(value, basestring) and len(value) > 80: + if self._isbasestring(value) and len(value) > 80: # If the string is big, save it to a file for later examination. - if isinstance(value, unicode): + if self._istext(value): value = value.encode('utf-8') path = self.OUTPUT_PATH + '%08d.txt' % self.random.randrange(1e8) - self.FileWriter(path).write('w', value) + self.FileWriter(path).write('wb', value) result += ' (file://' + path + ')' return result @@ -284,11 +296,13 @@ def __call__(self, *args): self.show(info.function, args, labels) return args and args[0] - def __div__(self, arg): # a tight-binding operator + def __truediv__(self, arg): # a tight-binding operator """Prints out and returns the argument.""" info = self.inspect.getframeinfo(self.sys._getframe(1)) self.show(info.function, [arg]) return arg + # Compat for Python 2 without from future import __division__ turned on + __div__ = __truediv__ __or__ = __div__ # a loose-binding operator q = __call__ # backward compatibility with @q.q -- 1.8.1.6 From d809647541025ac79b54ed6079272c11afd2daf0 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Thu, 11 Apr 2013 17:16:23 -0700 Subject: [PATCH 2/3] Encode to utf-8 at the FileWriter level so that we catch all undecodable strings --- q.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/q.py b/q.py index abe5b1e..3a99d2b 100644 --- a/q.py +++ b/q.py @@ -67,6 +67,7 @@ class Q(object): class FileWriter(object): """An object that appends to or overwrites a single file.""" + import sys def __init__(self, path): self.path = path @@ -76,7 +77,23 @@ def __init__(self, path): if open.__name__ == 'FakeFile': # dev_appserver's patched 'file' self.open = open.__bases__[0] # the original built-in 'file' + def _isbasestring(self, value): + if self.sys.version_info >= (3,): + return isinstance(value, (str, bytes)) + else: + return isinstance(value, basestring) + + def _istext(self, value): + if self.sys.version_info >= (3,): + return isinstance(value, str) + else: + return isinstance(value, unicode) + def write(self, mode, content): + if 'b' not in mode: + mode = '%sb' % mode + if self._isbasestring(content) and self._istext(content): + content = content.encode('utf-8') try: f = self.open(self.path, mode) f.write(content) @@ -155,29 +172,17 @@ def unindent(self, lines): indent = min(len(self.re.match(r'^ *', line).group()) for line in lines) return [line[indent:].rstrip() for line in lines] - def _isbasestring(self, value): - if self.sys.version_info >= (3,): - return isinstance(value, (str, bytes)) - else: - return isinstance(value, basestring) - - def _istext(self, value): - if self.sys.version_info >= (3,): - return isinstance(value, str) - else: - return isinstance(value, unicode) - def safe_repr(self, value): # TODO: Use colour to distinguish '...' elision from actual '...' chars. # TODO: Show a nicer repr for SRE.Match objects. # TODO: Show a nicer repr for big multiline strings. result = self.TEXT_REPR.repr(value) - if self._isbasestring(value) and len(value) > 80: + if self.writer.file_writer._isbasestring(value) and len(value) > 80: # If the string is big, save it to a file for later examination. - if self._istext(value): + if self.writer.file_writer._istext(value): value = value.encode('utf-8') path = self.OUTPUT_PATH + '%08d.txt' % self.random.randrange(1e8) - self.FileWriter(path).write('wb', value) + self.FileWriter(path).write('w', value) result += ' (file://' + path + ')' return result -- 1.8.1.6 From 20cf7bcd4587a603497781bd1cfa140b58950e27 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 6 May 2013 09:21:14 -0700 Subject: [PATCH 3/3] Switch from functions to variables to encapsulate python2/3 differences --- q.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/q.py b/q.py index 3a99d2b..9136313 100644 --- a/q.py +++ b/q.py @@ -44,6 +44,8 @@ __author__ = 'Ka-Ping Yee ' +import sys + # WARNING: Horrible abuse of sys.modules, __call__, __div__, __or__, inspect, # sys._getframe, and more! q's behaviour changes depending on the text of the # source code near its call site. Don't ever do this in real code! @@ -51,6 +53,14 @@ # These are reused below in both Q and Writer. ESCAPE_SEQUENCES = ['\x1b[0m'] + ['\x1b[3%dm' % i for i in range(1, 7)] +if sys.version_info >= (3,): + BASESTRING_TYPES = (str, bytes) + TEXT_TYPES = (str,) +else: + BASESTRING_TYPES = (basestring,) + TEXT_TYPES = (unicode,) + + # When we insert Q() into sys.modules, all the globals become None, so we # have to keep everything we use inside the Q class. class Q(object): @@ -65,9 +75,16 @@ class Q(object): NORMAL, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN = ESCAPE_SEQUENCES TEXT_REPR = pydoc.TextRepr() + # For portably converting strings between python2 and python3 + BASESTRING_TYPES = BASESTRING_TYPES + TEXT_TYPES = TEXT_TYPES + class FileWriter(object): """An object that appends to or overwrites a single file.""" import sys + # For portably converting strings between python2 and python3 + BASESTRING_TYPES = BASESTRING_TYPES + TEXT_TYPES = TEXT_TYPES def __init__(self, path): self.path = path @@ -77,22 +94,10 @@ def __init__(self, path): if open.__name__ == 'FakeFile': # dev_appserver's patched 'file' self.open = open.__bases__[0] # the original built-in 'file' - def _isbasestring(self, value): - if self.sys.version_info >= (3,): - return isinstance(value, (str, bytes)) - else: - return isinstance(value, basestring) - - def _istext(self, value): - if self.sys.version_info >= (3,): - return isinstance(value, str) - else: - return isinstance(value, unicode) - def write(self, mode, content): if 'b' not in mode: mode = '%sb' % mode - if self._isbasestring(content) and self._istext(content): + if isinstance(content, self.BASESTRING_TYPES) and isinstance(content, self.TEXT_TYPES): content = content.encode('utf-8') try: f = self.open(self.path, mode) @@ -177,9 +182,9 @@ def safe_repr(self, value): # TODO: Show a nicer repr for SRE.Match objects. # TODO: Show a nicer repr for big multiline strings. result = self.TEXT_REPR.repr(value) - if self.writer.file_writer._isbasestring(value) and len(value) > 80: + if isinstance(value, self.BASESTRING_TYPES) and len(value) > 80: # If the string is big, save it to a file for later examination. - if self.writer.file_writer._istext(value): + if isinstance(value, self.TEXT_TYPES): value = value.encode('utf-8') path = self.OUTPUT_PATH + '%08d.txt' % self.random.randrange(1e8) self.FileWriter(path).write('w', value) @@ -339,5 +344,4 @@ def d(self, depth=1): # Install the Q() object in sys.modules so that "import q" gives a callable q. -import sys sys.modules['q'] = Q() -- 1.8.1.6