Skip to content

Commit a2a934c

Browse files
add auto default to runsource
1 parent ebeffd7 commit a2a934c

File tree

2 files changed

+177
-30
lines changed

2 files changed

+177
-30
lines changed

bpython/repl.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -118,23 +118,41 @@ def runsource(self, source, filename=None, symbol='single',
118118
119119
In Python 3, source must be a unicode string
120120
In Python 2, source may be latin-1 bytestring or unicode string,
121-
following the interface of code.InteractiveInterpreter"""
122-
if encode == 'auto':
123-
encode = filename is None
124-
if encode and not py3:
125-
if isinstance(source, str):
126-
# encoding only makes sense for bytestrings
127-
assert isinstance(source, str)
128-
source = b'# coding: %s\n\n%s' % (self.encoding, source)
129-
else:
130-
# 2 blank lines still need to be added because this
131-
# interpreter always adds 2 lines to stack trace line
121+
following the interface of code.InteractiveInterpreter.
122+
123+
Because adding an encoding comment to a unicode string in Python 2
124+
would cause a syntax error to be thrown which would reference code
125+
the user did not write, setting encoding to True when source is a
126+
unicode string in Python 2 will throw a ValueError."""
127+
# str means bytestring in Py2
128+
if encode and not py3 and isinstance(source, unicode):
129+
if encode != 'auto':
130+
raise ValueError("can't add encoding line to unicode input")
131+
encode = False
132+
if encode and filename is not None:
133+
# files have encoding comments or implicit encoding of ASCII
134+
if encode != 'auto':
135+
raise ValueError("shouldn't add encoding line to file contents")
136+
encode = False
137+
138+
if encode and not py3 and isinstance(source, str):
139+
# encoding makes sense for bytestrings, so long as there
140+
# isn't already an encoding comment
141+
comment = inspection.get_encoding_comment(source)
142+
if comment:
143+
# keep the existing encoding comment, but add two lines
144+
# because this interp always adds 2 to stack trace line
132145
# numbers in Python 2
133-
comment = inspection.get_encoding_comment(source)
134-
if comment:
135-
source = source.replace(comment, u'%s\n\n' % comment, 1)
136-
else:
137-
source = u'\n\n' + source
146+
source = source.replace(comment, b'%s\n\n' % comment, 1)
147+
else:
148+
source = b'# coding: %s\n\n%s' % (self.encoding, source)
149+
elif not py3 and filename is None:
150+
# 2 blank lines still need to be added
151+
# because this interpreter always adds 2 to stack trace line
152+
# numbers in Python 2 when the filename is "<input>"
153+
newlines = u'\n\n' if isinstance(source, unicode) else b'\n\n'
154+
source = newlines + source
155+
# we know we're in Python 2 here, so ok to reference unicode
138156
if filename is None:
139157
filename = filename_for_console_input(source)
140158
with self.timer:

bpython/test/test_interpreter.py

Lines changed: 143 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from __future__ import unicode_literals
44

55
import sys
6+
import re
7+
from textwrap import dedent
68

79
from curtsies.fmtfuncs import bold, green, magenta, cyan, red, plain
810

@@ -13,15 +15,32 @@
1315
pypy = 'PyPy' in sys.version
1416

1517

18+
def remove_ansi(s):
19+
return re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]'.encode('ascii'), b'', s)
20+
21+
1622
class TestInterpreter(unittest.TestCase):
17-
def test_syntaxerror(self):
23+
def interp_errlog(self):
1824
i = interpreter.Interp()
1925
a = []
26+
i.write = a.append
27+
return i, a
28+
29+
def err_lineno(self, a):
30+
strings = [x.__unicode__() for x in a]
31+
print('looking for lineno')
32+
for line in reversed(strings):
33+
clean_line = remove_ansi(line)
34+
print(clean_line)
35+
m = re.search(r'line (\d+)[,]', clean_line)
36+
if m:
37+
print('found!', m.group(1))
38+
return int(m.group(1))
39+
return None
2040

21-
def append_to_a(message):
22-
a.append(message)
41+
def test_syntaxerror(self):
42+
i, a = self.interp_errlog()
2343

24-
i.write = append_to_a
2544
i.runsource('1.1.1.1')
2645

2746
if pypy:
@@ -41,13 +60,7 @@ def append_to_a(message):
4160
self.assertEquals(plain('').join(a), expected)
4261

4362
def test_traceback(self):
44-
i = interpreter.Interp()
45-
a = []
46-
47-
def append_to_a(message):
48-
a.append(message)
49-
50-
i.write = append_to_a
63+
i, a = self.interp_errlog()
5164

5265
def f():
5366
return 1 / 0
@@ -88,22 +101,22 @@ def test_runsource_bytes_over_128_syntax_error_py3(self):
88101
i = interpreter.Interp(encoding=b'latin-1')
89102
i.showsyntaxerror = mock.Mock(return_value=None)
90103

91-
i.runsource("a = b'\xfe'", encode=True)
104+
i.runsource("a = b'\xfe'")
92105
i.showsyntaxerror.assert_called_with(mock.ANY)
93106

94107
@unittest.skipIf(py3, "encode is Python 2 only")
95108
def test_runsource_bytes_over_128_syntax_error_py2(self):
96109
i = interpreter.Interp(encoding=b'latin-1')
97110

98-
i.runsource(b"a = b'\xfe'", encode=True)
111+
i.runsource(b"a = b'\xfe'")
99112
self.assertIsInstance(i.locals['a'], type(b''))
100113
self.assertEqual(i.locals['a'], b"\xfe")
101114

102115
@unittest.skipIf(py3, "encode is Python 2 only")
103116
def test_runsource_unicode(self):
104117
i = interpreter.Interp(encoding=b'latin-1')
105118

106-
i.runsource("a = u'\xfe'", encode=True)
119+
i.runsource("a = u'\xfe'")
107120
self.assertIsInstance(i.locals['a'], type(u''))
108121
self.assertEqual(i.locals['a'], u"\xfe")
109122

@@ -114,3 +127,119 @@ def test_getsource_works_on_interactively_defined_functions(self):
114127
import inspect
115128
inspected_source = inspect.getsource(i.locals['foo'])
116129
self.assertEquals(inspected_source, source)
130+
131+
@unittest.skipIf(py3, "encode only does anything in Python 2")
132+
def test_runsource_unicode_autoencode_and_noencode(self):
133+
"""error line numbers should be fixed"""
134+
135+
# Since correct behavior for unicode is the same
136+
# for auto and False, run the same tests
137+
for encode in ['auto', False]:
138+
i, a = self.interp_errlog()
139+
i.runsource(u'[1 + 1,\nabc]', encode=encode)
140+
self.assertEqual(self.err_lineno(a), 2)
141+
142+
i, a = self.interp_errlog()
143+
i.runsource(u'[1 + 1,\nabc]', encode=encode)
144+
self.assertEqual(self.err_lineno(a), 2)
145+
146+
i, a = self.interp_errlog()
147+
i.runsource(u'#encoding: utf-8\nabc', encode=encode)
148+
self.assertEqual(self.err_lineno(a), 2)
149+
150+
i, a = self.interp_errlog()
151+
i.runsource(u'#encoding: utf-8\nabc',
152+
filename='x.py', encode=encode)
153+
self.assertIn('SyntaxError: encoding',
154+
''.join(''.join(remove_ansi(x.__unicode__()) for x in a)))
155+
156+
@unittest.skipIf(py3, "encode only does anything in Python 2")
157+
def test_runsource_unicode_encode(self):
158+
i, _ = self.interp_errlog()
159+
with self.assertRaises(ValueError):
160+
i.runsource(u'1 + 1', encode=True)
161+
162+
i, _ = self.interp_errlog()
163+
with self.assertRaises(ValueError):
164+
i.runsource(u'1 + 1', filename='x.py', encode=True)
165+
166+
@unittest.skipIf(py3, "encode only does anything in Python 2")
167+
def test_runsource_bytestring_noencode(self):
168+
i, a = self.interp_errlog()
169+
i.runsource(b'[1 + 1,\nabc]', encode=False)
170+
self.assertEqual(self.err_lineno(a), 2)
171+
172+
i, a = self.interp_errlog()
173+
i.runsource(b'[1 + 1,\nabc]', filename='x.py', encode=False)
174+
self.assertEqual(self.err_lineno(a), 2)
175+
176+
i, a = self.interp_errlog()
177+
i.runsource(dedent(b'''\
178+
#encoding: utf-8
179+
180+
["%s",
181+
abc]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=False)
182+
self.assertEqual(self.err_lineno(a), 4)
183+
184+
i, a = self.interp_errlog()
185+
i.runsource(dedent(b'''\
186+
#encoding: utf-8
187+
188+
["%s",
189+
abc]''' % (u'åß∂ƒ'.encode('utf8'),)),
190+
filename='x.py', encode=False)
191+
self.assertEqual(self.err_lineno(a), 4)
192+
193+
@unittest.skipIf(py3, "encode only does anything in Python 2")
194+
def test_runsource_bytestring_encode(self):
195+
i, a = self.interp_errlog()
196+
i.runsource(b'[1 + 1,\nabc]', encode=True)
197+
self.assertEqual(self.err_lineno(a), 2)
198+
199+
i, a = self.interp_errlog()
200+
with self.assertRaises(ValueError):
201+
i.runsource(b'[1 + 1,\nabc]', filename='x.py', encode=True)
202+
203+
i, a = self.interp_errlog()
204+
i.runsource(dedent(b'''\
205+
#encoding: utf-8
206+
207+
[u"%s",
208+
abc]''' % (u'åß∂ƒ'.encode('utf8'),)), encode=True)
209+
self.assertEqual(self.err_lineno(a), 4)
210+
211+
i, a = self.interp_errlog()
212+
with self.assertRaises(ValueError):
213+
i.runsource(dedent(b'''\
214+
#encoding: utf-8
215+
216+
[u"%s",
217+
abc]''' % (u'åß∂ƒ'.encode('utf8'),)),
218+
filename='x.py',
219+
encode=True)
220+
221+
@unittest.skipIf(py3, "encode only does anything in Python 2")
222+
def test_runsource_bytestring_autoencode(self):
223+
i, a = self.interp_errlog()
224+
i.runsource(b'[1 + 1,\n abc]')
225+
self.assertEqual(self.err_lineno(a), 2)
226+
227+
i, a = self.interp_errlog()
228+
i.runsource(b'[1 + 1,\nabc]', filename='x.py')
229+
self.assertEqual(self.err_lineno(a), 2)
230+
231+
i, a = self.interp_errlog()
232+
i.runsource(dedent(b'''\
233+
#encoding: utf-8
234+
235+
[u"%s",
236+
abc]''' % (u'åß∂ƒ'.encode('utf8'),)))
237+
self.assertEqual(self.err_lineno(a), 4)
238+
239+
i, a = self.interp_errlog()
240+
i.runsource(dedent(b'''\
241+
#encoding: utf-8
242+
243+
[u"%s",
244+
abc]''' % (u'åß∂ƒ'.encode('utf8'),)))
245+
self.assertEqual(self.err_lineno(a), 4)

0 commit comments

Comments
 (0)