113 lines
2.8 KiB
Python
113 lines
2.8 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
Part of the astor library for Python AST manipulation.
|
||
|
|
||
|
License: 3-clause BSD
|
||
|
|
||
|
Copyright (c) 2015 Patrick Maupin
|
||
|
|
||
|
Pretty-print strings for the decompiler
|
||
|
|
||
|
We either return the repr() of the string,
|
||
|
or try to format it as a triple-quoted string.
|
||
|
|
||
|
This is a lot harder than you would think.
|
||
|
|
||
|
This has lots of Python 2 / Python 3 ugliness.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import re
|
||
|
|
||
|
try:
|
||
|
special_unicode = unicode
|
||
|
except NameError:
|
||
|
class special_unicode(object):
|
||
|
pass
|
||
|
|
||
|
try:
|
||
|
basestring = basestring
|
||
|
except NameError:
|
||
|
basestring = str
|
||
|
|
||
|
|
||
|
def _properly_indented(s, line_indent):
|
||
|
mylist = s.split('\n')[1:]
|
||
|
mylist = [x.rstrip() for x in mylist]
|
||
|
mylist = [x for x in mylist if x]
|
||
|
if not s:
|
||
|
return False
|
||
|
counts = [(len(x) - len(x.lstrip())) for x in mylist]
|
||
|
return counts and min(counts) >= line_indent
|
||
|
|
||
|
|
||
|
mysplit = re.compile(r'(\\|\"\"\"|\"$)').split
|
||
|
replacements = {'\\': '\\\\', '"""': '""\\"', '"': '\\"'}
|
||
|
|
||
|
|
||
|
def _prep_triple_quotes(s, mysplit=mysplit, replacements=replacements):
|
||
|
""" Split the string up and force-feed some replacements
|
||
|
to make sure it will round-trip OK
|
||
|
"""
|
||
|
|
||
|
s = mysplit(s)
|
||
|
s[1::2] = (replacements[x] for x in s[1::2])
|
||
|
return ''.join(s)
|
||
|
|
||
|
|
||
|
def string_triplequote_repr(s):
|
||
|
"""Return string's python representation in triple quotes.
|
||
|
"""
|
||
|
return '"""%s"""' % _prep_triple_quotes(s)
|
||
|
|
||
|
|
||
|
def pretty_string(s, embedded, current_line, uni_lit=False,
|
||
|
min_trip_str=20, max_line=100):
|
||
|
"""There are a lot of reasons why we might not want to or
|
||
|
be able to return a triple-quoted string. We can always
|
||
|
punt back to the default normal string.
|
||
|
"""
|
||
|
|
||
|
default = repr(s)
|
||
|
|
||
|
# Punt on abnormal strings
|
||
|
if (isinstance(s, special_unicode) or not isinstance(s, basestring)):
|
||
|
return default
|
||
|
if uni_lit and isinstance(s, bytes):
|
||
|
return 'b' + default
|
||
|
|
||
|
len_s = len(default)
|
||
|
|
||
|
if current_line.strip():
|
||
|
len_current = len(current_line)
|
||
|
second_line_start = s.find('\n') + 1
|
||
|
if embedded > 1 and not second_line_start:
|
||
|
return default
|
||
|
|
||
|
if len_s < min_trip_str:
|
||
|
return default
|
||
|
|
||
|
line_indent = len_current - len(current_line.lstrip())
|
||
|
|
||
|
# Could be on a line by itself...
|
||
|
if embedded and not second_line_start:
|
||
|
return default
|
||
|
|
||
|
total_len = len_current + len_s
|
||
|
if total_len < max_line and not _properly_indented(s, line_indent):
|
||
|
return default
|
||
|
|
||
|
fancy = string_triplequote_repr(s)
|
||
|
|
||
|
# Sometimes this doesn't work. One reason is that
|
||
|
# the AST has no understanding of whether \r\n was
|
||
|
# entered that way in the string or was a cr/lf in the
|
||
|
# file. So we punt just so we can round-trip properly.
|
||
|
|
||
|
try:
|
||
|
if eval(fancy) == s and '\r' not in fancy:
|
||
|
return fancy
|
||
|
except:
|
||
|
pass
|
||
|
return default
|