##// END OF EJS Templates
Fix miss-capturing of assign statement after a dedent....
Matthias Bussonnier -
Show More
@@ -13,7 +13,7 b' deprecated in 7.0.'
13 from codeop import compile_command
13 from codeop import compile_command
14 import re
14 import re
15 import tokenize
15 import tokenize
16 from typing import List, Tuple
16 from typing import List, Tuple, Union
17 import warnings
17 import warnings
18
18
19 _indent_re = re.compile(r'^[ \t]+')
19 _indent_re = re.compile(r'^[ \t]+')
@@ -87,7 +87,7 b' def cell_magic(lines):'
87 % (magic_name, first_line, body)]
87 % (magic_name, first_line, body)]
88
88
89
89
90 def _find_assign_op(token_line):
90 def _find_assign_op(token_line) -> Union[int, None]:
91 """Get the index of the first assignment in the line ('=' not inside brackets)
91 """Get the index of the first assignment in the line ('=' not inside brackets)
92
92
93 Note: We don't try to support multiple special assignment (a = b = %foo)
93 Note: We don't try to support multiple special assignment (a = b = %foo)
@@ -97,9 +97,9 b' def _find_assign_op(token_line):'
97 s = ti.string
97 s = ti.string
98 if s == '=' and paren_level == 0:
98 if s == '=' and paren_level == 0:
99 return i
99 return i
100 if s in '([{':
100 if s in {'(','[','{'}:
101 paren_level += 1
101 paren_level += 1
102 elif s in ')]}':
102 elif s in {')', ']', '}'}:
103 if paren_level > 0:
103 if paren_level > 0:
104 paren_level -= 1
104 paren_level -= 1
105
105
@@ -449,11 +449,14 b' class HelpEnd(TokenTransformBase):'
449
449
450 return lines_before + [new_line] + lines_after
450 return lines_before + [new_line] + lines_after
451
451
452 def make_tokens_by_line(lines):
452 def make_tokens_by_line(lines:List[str]):
453 """Tokenize a series of lines and group tokens by line.
453 """Tokenize a series of lines and group tokens by line.
454
454
455 The tokens for a multiline Python string or expression are
455 The tokens for a multiline Python string or expression are grouped as one
456 grouped as one line.
456 line. All lines except the last lines should keep their line ending ('\\n',
457 '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
458 for example when passing block of text to this function.
459
457 """
460 """
458 # NL tokens are used inside multiline expressions, but also after blank
461 # NL tokens are used inside multiline expressions, but also after blank
459 # lines or comments. This is intentional - see https://bugs.python.org/issue17061
462 # lines or comments. This is intentional - see https://bugs.python.org/issue17061
@@ -461,6 +464,8 b' def make_tokens_by_line(lines):'
461 # track parentheses level, similar to the internals of tokenize.
464 # track parentheses level, similar to the internals of tokenize.
462 NEWLINE, NL = tokenize.NEWLINE, tokenize.NL
465 NEWLINE, NL = tokenize.NEWLINE, tokenize.NL
463 tokens_by_line = [[]]
466 tokens_by_line = [[]]
467 if len(lines) > 1 and not lines[0].endswith(('\n', '\r', '\r\n', '\x0b', '\x0c')):
468 warnings.warn("`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified")
464 parenlev = 0
469 parenlev = 0
465 try:
470 try:
466 for token in tokenize.generate_tokens(iter(lines).__next__):
471 for token in tokenize.generate_tokens(iter(lines).__next__):
@@ -8,7 +8,7 b' import nose.tools as nt'
8 import string
8 import string
9
9
10 from IPython.core import inputtransformer2 as ipt2
10 from IPython.core import inputtransformer2 as ipt2
11 from IPython.core.inputtransformer2 import make_tokens_by_line
11 from IPython.core.inputtransformer2 import make_tokens_by_line, _find_assign_op
12
12
13 from textwrap import dedent
13 from textwrap import dedent
14
14
@@ -53,6 +53,22 b" b = get_ipython().getoutput('foo bar')"
53 g()
53 g()
54 """.splitlines(keepends=True))
54 """.splitlines(keepends=True))
55
55
56 #####
57
58 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\
59 def test():
60 for i in range(1):
61 print(i)
62 res =! ls
63 """.splitlines(keepends=True), (4, 7), '''\
64 def test():
65 for i in range(1):
66 print(i)
67 res =get_ipython().getoutput(\' ls\')
68 '''.splitlines(keepends=True))
69
70 ######
71
56 AUTOCALL_QUOTE = (
72 AUTOCALL_QUOTE = (
57 [",f 1 2 3\n"], (1, 0),
73 [",f 1 2 3\n"], (1, 0),
58 ['f("1", "2", "3")\n']
74 ['f("1", "2", "3")\n']
@@ -103,6 +119,7 b' b) = zip?'
103 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
119 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
104 )
120 )
105
121
122
106 def null_cleanup_transformer(lines):
123 def null_cleanup_transformer(lines):
107 """
124 """
108 A cleanup transform that returns an empty list.
125 A cleanup transform that returns an empty list.
@@ -144,18 +161,21 b' def test_continued_line():'
144 def test_find_assign_magic():
161 def test_find_assign_magic():
145 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
162 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
146 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
163 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
164 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
147
165
148 def test_transform_assign_magic():
166 def test_transform_assign_magic():
149 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
167 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
150
168
151 def test_find_assign_system():
169 def test_find_assign_system():
152 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
170 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
171 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
153 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
172 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
154 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
173 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
155 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
174 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
156
175
157 def test_transform_assign_system():
176 def test_transform_assign_system():
158 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
177 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
178 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
159
179
160 def test_find_magic_escape():
180 def test_find_magic_escape():
161 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
181 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
@@ -203,6 +223,17 b' def test_transform_help():'
203 tf = ipt2.HelpEnd((1, 0), (2, 8))
223 tf = ipt2.HelpEnd((1, 0), (2, 8))
204 nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2])
224 nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2])
205
225
226 def test_find_assign_op_dedent():
227 """
228 be carefull that empty token like dedent are not counted as parens
229 """
230 class Tk:
231 def __init__(self, s):
232 self.string = s
233
234 nt.assert_equal(_find_assign_op([Tk(s) for s in ('','a','=','b')]), 2)
235 nt.assert_equal(_find_assign_op([Tk(s) for s in ('','(', 'a','=','b', ')', '=' ,'5')]), 6)
236
206 def test_check_complete():
237 def test_check_complete():
207 cc = ipt2.TransformerManager().check_complete
238 cc = ipt2.TransformerManager().check_complete
208 nt.assert_equal(cc("a = 1"), ('complete', None))
239 nt.assert_equal(cc("a = 1"), ('complete', None))
General Comments 0
You need to be logged in to leave comments. Login now