##// END OF EJS Templates
add docstring, and emit deprecation warnings
Matthias Bussonnier -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,768 +1,773 b''
1 """DEPRECATED: Input handling and transformation machinery.
1 """DEPRECATED: Input handling and transformation machinery.
2
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
4
4
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
6 input from a line-oriented frontend is complete and should be executed, and when
6 input from a line-oriented frontend is complete and should be executed, and when
7 the user should be prompted for another line of code instead. The name 'input
7 the user should be prompted for another line of code instead. The name 'input
8 splitter' is largely for historical reasons.
8 splitter' is largely for historical reasons.
9
9
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
11 with full support for the extended IPython syntax (magics, system calls, etc).
11 with full support for the extended IPython syntax (magics, system calls, etc).
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
14 and stores the results.
14 and stores the results.
15
15
16 For more details, see the class docstrings below.
16 For more details, see the class docstrings below.
17 """
17 """
18
18
19 from warnings import warn
20
21 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
22 DeprecationWarning)
23
19 # Copyright (c) IPython Development Team.
24 # Copyright (c) IPython Development Team.
20 # Distributed under the terms of the Modified BSD License.
25 # Distributed under the terms of the Modified BSD License.
21 import ast
26 import ast
22 import codeop
27 import codeop
23 import io
28 import io
24 import re
29 import re
25 import sys
30 import sys
26 import tokenize
31 import tokenize
27 import warnings
32 import warnings
28
33
29 from IPython.utils.py3compat import cast_unicode
34 from IPython.utils.py3compat import cast_unicode
30 from IPython.core.inputtransformer import (leading_indent,
35 from IPython.core.inputtransformer import (leading_indent,
31 classic_prompt,
36 classic_prompt,
32 ipy_prompt,
37 ipy_prompt,
33 cellmagic,
38 cellmagic,
34 assemble_logical_lines,
39 assemble_logical_lines,
35 help_end,
40 help_end,
36 escaped_commands,
41 escaped_commands,
37 assign_from_magic,
42 assign_from_magic,
38 assign_from_system,
43 assign_from_system,
39 assemble_python_lines,
44 assemble_python_lines,
40 )
45 )
41
46
42 # These are available in this module for backwards compatibility.
47 # These are available in this module for backwards compatibility.
43 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
48 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
44 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
49 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
45 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
50 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
46
51
47 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
48 # Utilities
53 # Utilities
49 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
50
55
51 # FIXME: These are general-purpose utilities that later can be moved to the
56 # FIXME: These are general-purpose utilities that later can be moved to the
52 # general ward. Kept here for now because we're being very strict about test
57 # general ward. Kept here for now because we're being very strict about test
53 # coverage with this code, and this lets us ensure that we keep 100% coverage
58 # coverage with this code, and this lets us ensure that we keep 100% coverage
54 # while developing.
59 # while developing.
55
60
56 # compiled regexps for autoindent management
61 # compiled regexps for autoindent management
57 dedent_re = re.compile('|'.join([
62 dedent_re = re.compile('|'.join([
58 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
63 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
59 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
64 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
60 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
65 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
61 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
66 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
62 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
67 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
63 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
68 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
64 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
69 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
65 ]))
70 ]))
66 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
71 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
67
72
68 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
73 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
69 # before pure comments
74 # before pure comments
70 comment_line_re = re.compile('^\s*\#')
75 comment_line_re = re.compile('^\s*\#')
71
76
72
77
73 def num_ini_spaces(s):
78 def num_ini_spaces(s):
74 """Return the number of initial spaces in a string.
79 """Return the number of initial spaces in a string.
75
80
76 Note that tabs are counted as a single space. For now, we do *not* support
81 Note that tabs are counted as a single space. For now, we do *not* support
77 mixing of tabs and spaces in the user's input.
82 mixing of tabs and spaces in the user's input.
78
83
79 Parameters
84 Parameters
80 ----------
85 ----------
81 s : string
86 s : string
82
87
83 Returns
88 Returns
84 -------
89 -------
85 n : int
90 n : int
86 """
91 """
87
92
88 ini_spaces = ini_spaces_re.match(s)
93 ini_spaces = ini_spaces_re.match(s)
89 if ini_spaces:
94 if ini_spaces:
90 return ini_spaces.end()
95 return ini_spaces.end()
91 else:
96 else:
92 return 0
97 return 0
93
98
94 # Fake token types for partial_tokenize:
99 # Fake token types for partial_tokenize:
95 INCOMPLETE_STRING = tokenize.N_TOKENS
100 INCOMPLETE_STRING = tokenize.N_TOKENS
96 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
101 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
97
102
98 # The 2 classes below have the same API as TokenInfo, but don't try to look up
103 # The 2 classes below have the same API as TokenInfo, but don't try to look up
99 # a token type name that they won't find.
104 # a token type name that they won't find.
100 class IncompleteString:
105 class IncompleteString:
101 type = exact_type = INCOMPLETE_STRING
106 type = exact_type = INCOMPLETE_STRING
102 def __init__(self, s, start, end, line):
107 def __init__(self, s, start, end, line):
103 self.s = s
108 self.s = s
104 self.start = start
109 self.start = start
105 self.end = end
110 self.end = end
106 self.line = line
111 self.line = line
107
112
108 class InMultilineStatement:
113 class InMultilineStatement:
109 type = exact_type = IN_MULTILINE_STATEMENT
114 type = exact_type = IN_MULTILINE_STATEMENT
110 def __init__(self, pos, line):
115 def __init__(self, pos, line):
111 self.s = ''
116 self.s = ''
112 self.start = self.end = pos
117 self.start = self.end = pos
113 self.line = line
118 self.line = line
114
119
115 def partial_tokens(s):
120 def partial_tokens(s):
116 """Iterate over tokens from a possibly-incomplete string of code.
121 """Iterate over tokens from a possibly-incomplete string of code.
117
122
118 This adds two special token types: INCOMPLETE_STRING and
123 This adds two special token types: INCOMPLETE_STRING and
119 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
124 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
120 represent the two main ways for code to be incomplete.
125 represent the two main ways for code to be incomplete.
121 """
126 """
122 readline = io.StringIO(s).readline
127 readline = io.StringIO(s).readline
123 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
128 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
124 try:
129 try:
125 for token in tokenize.generate_tokens(readline):
130 for token in tokenize.generate_tokens(readline):
126 yield token
131 yield token
127 except tokenize.TokenError as e:
132 except tokenize.TokenError as e:
128 # catch EOF error
133 # catch EOF error
129 lines = s.splitlines(keepends=True)
134 lines = s.splitlines(keepends=True)
130 end = len(lines), len(lines[-1])
135 end = len(lines), len(lines[-1])
131 if 'multi-line string' in e.args[0]:
136 if 'multi-line string' in e.args[0]:
132 l, c = start = token.end
137 l, c = start = token.end
133 s = lines[l-1][c:] + ''.join(lines[l:])
138 s = lines[l-1][c:] + ''.join(lines[l:])
134 yield IncompleteString(s, start, end, lines[-1])
139 yield IncompleteString(s, start, end, lines[-1])
135 elif 'multi-line statement' in e.args[0]:
140 elif 'multi-line statement' in e.args[0]:
136 yield InMultilineStatement(end, lines[-1])
141 yield InMultilineStatement(end, lines[-1])
137 else:
142 else:
138 raise
143 raise
139
144
140 def find_next_indent(code):
145 def find_next_indent(code):
141 """Find the number of spaces for the next line of indentation"""
146 """Find the number of spaces for the next line of indentation"""
142 tokens = list(partial_tokens(code))
147 tokens = list(partial_tokens(code))
143 if tokens[-1].type == tokenize.ENDMARKER:
148 if tokens[-1].type == tokenize.ENDMARKER:
144 tokens.pop()
149 tokens.pop()
145 if not tokens:
150 if not tokens:
146 return 0
151 return 0
147 while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}):
152 while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}):
148 tokens.pop()
153 tokens.pop()
149
154
150 if tokens[-1].type == INCOMPLETE_STRING:
155 if tokens[-1].type == INCOMPLETE_STRING:
151 # Inside a multiline string
156 # Inside a multiline string
152 return 0
157 return 0
153
158
154 # Find the indents used before
159 # Find the indents used before
155 prev_indents = [0]
160 prev_indents = [0]
156 def _add_indent(n):
161 def _add_indent(n):
157 if n != prev_indents[-1]:
162 if n != prev_indents[-1]:
158 prev_indents.append(n)
163 prev_indents.append(n)
159
164
160 tokiter = iter(tokens)
165 tokiter = iter(tokens)
161 for tok in tokiter:
166 for tok in tokiter:
162 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
167 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
163 _add_indent(tok.end[1])
168 _add_indent(tok.end[1])
164 elif (tok.type == tokenize.NL):
169 elif (tok.type == tokenize.NL):
165 try:
170 try:
166 _add_indent(next(tokiter).start[1])
171 _add_indent(next(tokiter).start[1])
167 except StopIteration:
172 except StopIteration:
168 break
173 break
169
174
170 last_indent = prev_indents.pop()
175 last_indent = prev_indents.pop()
171
176
172 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
177 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
173 if tokens[-1].type == IN_MULTILINE_STATEMENT:
178 if tokens[-1].type == IN_MULTILINE_STATEMENT:
174 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
179 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
175 return last_indent + 4
180 return last_indent + 4
176 return last_indent
181 return last_indent
177
182
178 if tokens[-1].exact_type == tokenize.COLON:
183 if tokens[-1].exact_type == tokenize.COLON:
179 # Line ends with colon - indent
184 # Line ends with colon - indent
180 return last_indent + 4
185 return last_indent + 4
181
186
182 if last_indent:
187 if last_indent:
183 # Examine the last line for dedent cues - statements like return or
188 # Examine the last line for dedent cues - statements like return or
184 # raise which normally end a block of code.
189 # raise which normally end a block of code.
185 last_line_starts = 0
190 last_line_starts = 0
186 for i, tok in enumerate(tokens):
191 for i, tok in enumerate(tokens):
187 if tok.type == tokenize.NEWLINE:
192 if tok.type == tokenize.NEWLINE:
188 last_line_starts = i + 1
193 last_line_starts = i + 1
189
194
190 last_line_tokens = tokens[last_line_starts:]
195 last_line_tokens = tokens[last_line_starts:]
191 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
196 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
192 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
197 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
193 # Find the most recent indentation less than the current level
198 # Find the most recent indentation less than the current level
194 for indent in reversed(prev_indents):
199 for indent in reversed(prev_indents):
195 if indent < last_indent:
200 if indent < last_indent:
196 return indent
201 return indent
197
202
198 return last_indent
203 return last_indent
199
204
200
205
201 def last_blank(src):
206 def last_blank(src):
202 """Determine if the input source ends in a blank.
207 """Determine if the input source ends in a blank.
203
208
204 A blank is either a newline or a line consisting of whitespace.
209 A blank is either a newline or a line consisting of whitespace.
205
210
206 Parameters
211 Parameters
207 ----------
212 ----------
208 src : string
213 src : string
209 A single or multiline string.
214 A single or multiline string.
210 """
215 """
211 if not src: return False
216 if not src: return False
212 ll = src.splitlines()[-1]
217 ll = src.splitlines()[-1]
213 return (ll == '') or ll.isspace()
218 return (ll == '') or ll.isspace()
214
219
215
220
216 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
221 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
217 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
222 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
218
223
219 def last_two_blanks(src):
224 def last_two_blanks(src):
220 """Determine if the input source ends in two blanks.
225 """Determine if the input source ends in two blanks.
221
226
222 A blank is either a newline or a line consisting of whitespace.
227 A blank is either a newline or a line consisting of whitespace.
223
228
224 Parameters
229 Parameters
225 ----------
230 ----------
226 src : string
231 src : string
227 A single or multiline string.
232 A single or multiline string.
228 """
233 """
229 if not src: return False
234 if not src: return False
230 # The logic here is tricky: I couldn't get a regexp to work and pass all
235 # The logic here is tricky: I couldn't get a regexp to work and pass all
231 # the tests, so I took a different approach: split the source by lines,
236 # the tests, so I took a different approach: split the source by lines,
232 # grab the last two and prepend '###\n' as a stand-in for whatever was in
237 # grab the last two and prepend '###\n' as a stand-in for whatever was in
233 # the body before the last two lines. Then, with that structure, it's
238 # the body before the last two lines. Then, with that structure, it's
234 # possible to analyze with two regexps. Not the most elegant solution, but
239 # possible to analyze with two regexps. Not the most elegant solution, but
235 # it works. If anyone tries to change this logic, make sure to validate
240 # it works. If anyone tries to change this logic, make sure to validate
236 # the whole test suite first!
241 # the whole test suite first!
237 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
242 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
238 return (bool(last_two_blanks_re.match(new_src)) or
243 return (bool(last_two_blanks_re.match(new_src)) or
239 bool(last_two_blanks_re2.match(new_src)) )
244 bool(last_two_blanks_re2.match(new_src)) )
240
245
241
246
242 def remove_comments(src):
247 def remove_comments(src):
243 """Remove all comments from input source.
248 """Remove all comments from input source.
244
249
245 Note: comments are NOT recognized inside of strings!
250 Note: comments are NOT recognized inside of strings!
246
251
247 Parameters
252 Parameters
248 ----------
253 ----------
249 src : string
254 src : string
250 A single or multiline input string.
255 A single or multiline input string.
251
256
252 Returns
257 Returns
253 -------
258 -------
254 String with all Python comments removed.
259 String with all Python comments removed.
255 """
260 """
256
261
257 return re.sub('#.*', '', src)
262 return re.sub('#.*', '', src)
258
263
259
264
260 def get_input_encoding():
265 def get_input_encoding():
261 """Return the default standard input encoding.
266 """Return the default standard input encoding.
262
267
263 If sys.stdin has no encoding, 'ascii' is returned."""
268 If sys.stdin has no encoding, 'ascii' is returned."""
264 # There are strange environments for which sys.stdin.encoding is None. We
269 # There are strange environments for which sys.stdin.encoding is None. We
265 # ensure that a valid encoding is returned.
270 # ensure that a valid encoding is returned.
266 encoding = getattr(sys.stdin, 'encoding', None)
271 encoding = getattr(sys.stdin, 'encoding', None)
267 if encoding is None:
272 if encoding is None:
268 encoding = 'ascii'
273 encoding = 'ascii'
269 return encoding
274 return encoding
270
275
271 #-----------------------------------------------------------------------------
276 #-----------------------------------------------------------------------------
272 # Classes and functions for normal Python syntax handling
277 # Classes and functions for normal Python syntax handling
273 #-----------------------------------------------------------------------------
278 #-----------------------------------------------------------------------------
274
279
275 class InputSplitter(object):
280 class InputSplitter(object):
276 r"""An object that can accumulate lines of Python source before execution.
281 r"""An object that can accumulate lines of Python source before execution.
277
282
278 This object is designed to be fed python source line-by-line, using
283 This object is designed to be fed python source line-by-line, using
279 :meth:`push`. It will return on each push whether the currently pushed
284 :meth:`push`. It will return on each push whether the currently pushed
280 code could be executed already. In addition, it provides a method called
285 code could be executed already. In addition, it provides a method called
281 :meth:`push_accepts_more` that can be used to query whether more input
286 :meth:`push_accepts_more` that can be used to query whether more input
282 can be pushed into a single interactive block.
287 can be pushed into a single interactive block.
283
288
284 This is a simple example of how an interactive terminal-based client can use
289 This is a simple example of how an interactive terminal-based client can use
285 this tool::
290 this tool::
286
291
287 isp = InputSplitter()
292 isp = InputSplitter()
288 while isp.push_accepts_more():
293 while isp.push_accepts_more():
289 indent = ' '*isp.indent_spaces
294 indent = ' '*isp.indent_spaces
290 prompt = '>>> ' + indent
295 prompt = '>>> ' + indent
291 line = indent + raw_input(prompt)
296 line = indent + raw_input(prompt)
292 isp.push(line)
297 isp.push(line)
293 print 'Input source was:\n', isp.source_reset(),
298 print 'Input source was:\n', isp.source_reset(),
294 """
299 """
295 # A cache for storing the current indentation
300 # A cache for storing the current indentation
296 # The first value stores the most recently processed source input
301 # The first value stores the most recently processed source input
297 # The second value is the number of spaces for the current indentation
302 # The second value is the number of spaces for the current indentation
298 # If self.source matches the first value, the second value is a valid
303 # If self.source matches the first value, the second value is a valid
299 # current indentation. Otherwise, the cache is invalid and the indentation
304 # current indentation. Otherwise, the cache is invalid and the indentation
300 # must be recalculated.
305 # must be recalculated.
301 _indent_spaces_cache = None, None
306 _indent_spaces_cache = None, None
302 # String, indicating the default input encoding. It is computed by default
307 # String, indicating the default input encoding. It is computed by default
303 # at initialization time via get_input_encoding(), but it can be reset by a
308 # at initialization time via get_input_encoding(), but it can be reset by a
304 # client with specific knowledge of the encoding.
309 # client with specific knowledge of the encoding.
305 encoding = ''
310 encoding = ''
306 # String where the current full source input is stored, properly encoded.
311 # String where the current full source input is stored, properly encoded.
307 # Reading this attribute is the normal way of querying the currently pushed
312 # Reading this attribute is the normal way of querying the currently pushed
308 # source code, that has been properly encoded.
313 # source code, that has been properly encoded.
309 source = ''
314 source = ''
310 # Code object corresponding to the current source. It is automatically
315 # Code object corresponding to the current source. It is automatically
311 # synced to the source, so it can be queried at any time to obtain the code
316 # synced to the source, so it can be queried at any time to obtain the code
312 # object; it will be None if the source doesn't compile to valid Python.
317 # object; it will be None if the source doesn't compile to valid Python.
313 code = None
318 code = None
314
319
315 # Private attributes
320 # Private attributes
316
321
317 # List with lines of input accumulated so far
322 # List with lines of input accumulated so far
318 _buffer = None
323 _buffer = None
319 # Command compiler
324 # Command compiler
320 _compile = None
325 _compile = None
321 # Boolean indicating whether the current block is complete
326 # Boolean indicating whether the current block is complete
322 _is_complete = None
327 _is_complete = None
323 # Boolean indicating whether the current block has an unrecoverable syntax error
328 # Boolean indicating whether the current block has an unrecoverable syntax error
324 _is_invalid = False
329 _is_invalid = False
325
330
326 def __init__(self):
331 def __init__(self):
327 """Create a new InputSplitter instance.
332 """Create a new InputSplitter instance.
328 """
333 """
329 self._buffer = []
334 self._buffer = []
330 self._compile = codeop.CommandCompiler()
335 self._compile = codeop.CommandCompiler()
331 self.encoding = get_input_encoding()
336 self.encoding = get_input_encoding()
332
337
333 def reset(self):
338 def reset(self):
334 """Reset the input buffer and associated state."""
339 """Reset the input buffer and associated state."""
335 self._buffer[:] = []
340 self._buffer[:] = []
336 self.source = ''
341 self.source = ''
337 self.code = None
342 self.code = None
338 self._is_complete = False
343 self._is_complete = False
339 self._is_invalid = False
344 self._is_invalid = False
340
345
341 def source_reset(self):
346 def source_reset(self):
342 """Return the input source and perform a full reset.
347 """Return the input source and perform a full reset.
343 """
348 """
344 out = self.source
349 out = self.source
345 self.reset()
350 self.reset()
346 return out
351 return out
347
352
348 def check_complete(self, source):
353 def check_complete(self, source):
349 """Return whether a block of code is ready to execute, or should be continued
354 """Return whether a block of code is ready to execute, or should be continued
350
355
351 This is a non-stateful API, and will reset the state of this InputSplitter.
356 This is a non-stateful API, and will reset the state of this InputSplitter.
352
357
353 Parameters
358 Parameters
354 ----------
359 ----------
355 source : string
360 source : string
356 Python input code, which can be multiline.
361 Python input code, which can be multiline.
357
362
358 Returns
363 Returns
359 -------
364 -------
360 status : str
365 status : str
361 One of 'complete', 'incomplete', or 'invalid' if source is not a
366 One of 'complete', 'incomplete', or 'invalid' if source is not a
362 prefix of valid code.
367 prefix of valid code.
363 indent_spaces : int or None
368 indent_spaces : int or None
364 The number of spaces by which to indent the next line of code. If
369 The number of spaces by which to indent the next line of code. If
365 status is not 'incomplete', this is None.
370 status is not 'incomplete', this is None.
366 """
371 """
367 self.reset()
372 self.reset()
368 try:
373 try:
369 self.push(source)
374 self.push(source)
370 except SyntaxError:
375 except SyntaxError:
371 # Transformers in IPythonInputSplitter can raise SyntaxError,
376 # Transformers in IPythonInputSplitter can raise SyntaxError,
372 # which push() will not catch.
377 # which push() will not catch.
373 return 'invalid', None
378 return 'invalid', None
374 else:
379 else:
375 if self._is_invalid:
380 if self._is_invalid:
376 return 'invalid', None
381 return 'invalid', None
377 elif self.push_accepts_more():
382 elif self.push_accepts_more():
378 return 'incomplete', self.get_indent_spaces()
383 return 'incomplete', self.get_indent_spaces()
379 else:
384 else:
380 return 'complete', None
385 return 'complete', None
381 finally:
386 finally:
382 self.reset()
387 self.reset()
383
388
384 def push(self, lines):
389 def push(self, lines):
385 """Push one or more lines of input.
390 """Push one or more lines of input.
386
391
387 This stores the given lines and returns a status code indicating
392 This stores the given lines and returns a status code indicating
388 whether the code forms a complete Python block or not.
393 whether the code forms a complete Python block or not.
389
394
390 Any exceptions generated in compilation are swallowed, but if an
395 Any exceptions generated in compilation are swallowed, but if an
391 exception was produced, the method returns True.
396 exception was produced, the method returns True.
392
397
393 Parameters
398 Parameters
394 ----------
399 ----------
395 lines : string
400 lines : string
396 One or more lines of Python input.
401 One or more lines of Python input.
397
402
398 Returns
403 Returns
399 -------
404 -------
400 is_complete : boolean
405 is_complete : boolean
401 True if the current input source (the result of the current input
406 True if the current input source (the result of the current input
402 plus prior inputs) forms a complete Python execution block. Note that
407 plus prior inputs) forms a complete Python execution block. Note that
403 this value is also stored as a private attribute (``_is_complete``), so it
408 this value is also stored as a private attribute (``_is_complete``), so it
404 can be queried at any time.
409 can be queried at any time.
405 """
410 """
406 self._store(lines)
411 self._store(lines)
407 source = self.source
412 source = self.source
408
413
409 # Before calling _compile(), reset the code object to None so that if an
414 # Before calling _compile(), reset the code object to None so that if an
410 # exception is raised in compilation, we don't mislead by having
415 # exception is raised in compilation, we don't mislead by having
411 # inconsistent code/source attributes.
416 # inconsistent code/source attributes.
412 self.code, self._is_complete = None, None
417 self.code, self._is_complete = None, None
413 self._is_invalid = False
418 self._is_invalid = False
414
419
415 # Honor termination lines properly
420 # Honor termination lines properly
416 if source.endswith('\\\n'):
421 if source.endswith('\\\n'):
417 return False
422 return False
418
423
419 try:
424 try:
420 with warnings.catch_warnings():
425 with warnings.catch_warnings():
421 warnings.simplefilter('error', SyntaxWarning)
426 warnings.simplefilter('error', SyntaxWarning)
422 self.code = self._compile(source, symbol="exec")
427 self.code = self._compile(source, symbol="exec")
423 # Invalid syntax can produce any of a number of different errors from
428 # Invalid syntax can produce any of a number of different errors from
424 # inside the compiler, so we have to catch them all. Syntax errors
429 # inside the compiler, so we have to catch them all. Syntax errors
425 # immediately produce a 'ready' block, so the invalid Python can be
430 # immediately produce a 'ready' block, so the invalid Python can be
426 # sent to the kernel for evaluation with possible ipython
431 # sent to the kernel for evaluation with possible ipython
427 # special-syntax conversion.
432 # special-syntax conversion.
428 except (SyntaxError, OverflowError, ValueError, TypeError,
433 except (SyntaxError, OverflowError, ValueError, TypeError,
429 MemoryError, SyntaxWarning):
434 MemoryError, SyntaxWarning):
430 self._is_complete = True
435 self._is_complete = True
431 self._is_invalid = True
436 self._is_invalid = True
432 else:
437 else:
433 # Compilation didn't produce any exceptions (though it may not have
438 # Compilation didn't produce any exceptions (though it may not have
434 # given a complete code object)
439 # given a complete code object)
435 self._is_complete = self.code is not None
440 self._is_complete = self.code is not None
436
441
437 return self._is_complete
442 return self._is_complete
438
443
439 def push_accepts_more(self):
444 def push_accepts_more(self):
440 """Return whether a block of interactive input can accept more input.
445 """Return whether a block of interactive input can accept more input.
441
446
442 This method is meant to be used by line-oriented frontends, who need to
447 This method is meant to be used by line-oriented frontends, who need to
443 guess whether a block is complete or not based solely on prior and
448 guess whether a block is complete or not based solely on prior and
444 current input lines. The InputSplitter considers it has a complete
449 current input lines. The InputSplitter considers it has a complete
445 interactive block and will not accept more input when either:
450 interactive block and will not accept more input when either:
446
451
447 * A SyntaxError is raised
452 * A SyntaxError is raised
448
453
449 * The code is complete and consists of a single line or a single
454 * The code is complete and consists of a single line or a single
450 non-compound statement
455 non-compound statement
451
456
452 * The code is complete and has a blank line at the end
457 * The code is complete and has a blank line at the end
453
458
454 If the current input produces a syntax error, this method immediately
459 If the current input produces a syntax error, this method immediately
455 returns False but does *not* raise the syntax error exception, as
460 returns False but does *not* raise the syntax error exception, as
456 typically clients will want to send invalid syntax to an execution
461 typically clients will want to send invalid syntax to an execution
457 backend which might convert the invalid syntax into valid Python via
462 backend which might convert the invalid syntax into valid Python via
458 one of the dynamic IPython mechanisms.
463 one of the dynamic IPython mechanisms.
459 """
464 """
460
465
461 # With incomplete input, unconditionally accept more
466 # With incomplete input, unconditionally accept more
462 # A syntax error also sets _is_complete to True - see push()
467 # A syntax error also sets _is_complete to True - see push()
463 if not self._is_complete:
468 if not self._is_complete:
464 #print("Not complete") # debug
469 #print("Not complete") # debug
465 return True
470 return True
466
471
467 # The user can make any (complete) input execute by leaving a blank line
472 # The user can make any (complete) input execute by leaving a blank line
468 last_line = self.source.splitlines()[-1]
473 last_line = self.source.splitlines()[-1]
469 if (not last_line) or last_line.isspace():
474 if (not last_line) or last_line.isspace():
470 #print("Blank line") # debug
475 #print("Blank line") # debug
471 return False
476 return False
472
477
473 # If there's just a single line or AST node, and we're flush left, as is
478 # If there's just a single line or AST node, and we're flush left, as is
474 # the case after a simple statement such as 'a=1', we want to execute it
479 # the case after a simple statement such as 'a=1', we want to execute it
475 # straight away.
480 # straight away.
476 if self.get_indent_spaces() == 0:
481 if self.get_indent_spaces() == 0:
477 if len(self.source.splitlines()) <= 1:
482 if len(self.source.splitlines()) <= 1:
478 return False
483 return False
479
484
480 try:
485 try:
481 code_ast = ast.parse(u''.join(self._buffer))
486 code_ast = ast.parse(u''.join(self._buffer))
482 except Exception:
487 except Exception:
483 #print("Can't parse AST") # debug
488 #print("Can't parse AST") # debug
484 return False
489 return False
485 else:
490 else:
486 if len(code_ast.body) == 1 and \
491 if len(code_ast.body) == 1 and \
487 not hasattr(code_ast.body[0], 'body'):
492 not hasattr(code_ast.body[0], 'body'):
488 #print("Simple statement") # debug
493 #print("Simple statement") # debug
489 return False
494 return False
490
495
491 # General fallback - accept more code
496 # General fallback - accept more code
492 return True
497 return True
493
498
494 def get_indent_spaces(self):
499 def get_indent_spaces(self):
495 sourcefor, n = self._indent_spaces_cache
500 sourcefor, n = self._indent_spaces_cache
496 if sourcefor == self.source:
501 if sourcefor == self.source:
497 return n
502 return n
498
503
499 # self.source always has a trailing newline
504 # self.source always has a trailing newline
500 n = find_next_indent(self.source[:-1])
505 n = find_next_indent(self.source[:-1])
501 self._indent_spaces_cache = (self.source, n)
506 self._indent_spaces_cache = (self.source, n)
502 return n
507 return n
503
508
504 # Backwards compatibility. I think all code that used .indent_spaces was
509 # Backwards compatibility. I think all code that used .indent_spaces was
505 # inside IPython, but we can leave this here until IPython 7 in case any
510 # inside IPython, but we can leave this here until IPython 7 in case any
506 # other modules are using it. -TK, November 2017
511 # other modules are using it. -TK, November 2017
507 indent_spaces = property(get_indent_spaces)
512 indent_spaces = property(get_indent_spaces)
508
513
509 def _store(self, lines, buffer=None, store='source'):
514 def _store(self, lines, buffer=None, store='source'):
510 """Store one or more lines of input.
515 """Store one or more lines of input.
511
516
512 If input lines are not newline-terminated, a newline is automatically
517 If input lines are not newline-terminated, a newline is automatically
513 appended."""
518 appended."""
514
519
515 if buffer is None:
520 if buffer is None:
516 buffer = self._buffer
521 buffer = self._buffer
517
522
518 if lines.endswith('\n'):
523 if lines.endswith('\n'):
519 buffer.append(lines)
524 buffer.append(lines)
520 else:
525 else:
521 buffer.append(lines+'\n')
526 buffer.append(lines+'\n')
522 setattr(self, store, self._set_source(buffer))
527 setattr(self, store, self._set_source(buffer))
523
528
524 def _set_source(self, buffer):
529 def _set_source(self, buffer):
525 return u''.join(buffer)
530 return u''.join(buffer)
526
531
527
532
528 class IPythonInputSplitter(InputSplitter):
533 class IPythonInputSplitter(InputSplitter):
529 """An input splitter that recognizes all of IPython's special syntax."""
534 """An input splitter that recognizes all of IPython's special syntax."""
530
535
531 # String with raw, untransformed input.
536 # String with raw, untransformed input.
532 source_raw = ''
537 source_raw = ''
533
538
534 # Flag to track when a transformer has stored input that it hasn't given
539 # Flag to track when a transformer has stored input that it hasn't given
535 # back yet.
540 # back yet.
536 transformer_accumulating = False
541 transformer_accumulating = False
537
542
538 # Flag to track when assemble_python_lines has stored input that it hasn't
543 # Flag to track when assemble_python_lines has stored input that it hasn't
539 # given back yet.
544 # given back yet.
540 within_python_line = False
545 within_python_line = False
541
546
542 # Private attributes
547 # Private attributes
543
548
544 # List with lines of raw input accumulated so far.
549 # List with lines of raw input accumulated so far.
545 _buffer_raw = None
550 _buffer_raw = None
546
551
547 def __init__(self, line_input_checker=True, physical_line_transforms=None,
552 def __init__(self, line_input_checker=True, physical_line_transforms=None,
548 logical_line_transforms=None, python_line_transforms=None):
553 logical_line_transforms=None, python_line_transforms=None):
549 super(IPythonInputSplitter, self).__init__()
554 super(IPythonInputSplitter, self).__init__()
550 self._buffer_raw = []
555 self._buffer_raw = []
551 self._validate = True
556 self._validate = True
552
557
553 if physical_line_transforms is not None:
558 if physical_line_transforms is not None:
554 self.physical_line_transforms = physical_line_transforms
559 self.physical_line_transforms = physical_line_transforms
555 else:
560 else:
556 self.physical_line_transforms = [
561 self.physical_line_transforms = [
557 leading_indent(),
562 leading_indent(),
558 classic_prompt(),
563 classic_prompt(),
559 ipy_prompt(),
564 ipy_prompt(),
560 cellmagic(end_on_blank_line=line_input_checker),
565 cellmagic(end_on_blank_line=line_input_checker),
561 ]
566 ]
562
567
563 self.assemble_logical_lines = assemble_logical_lines()
568 self.assemble_logical_lines = assemble_logical_lines()
564 if logical_line_transforms is not None:
569 if logical_line_transforms is not None:
565 self.logical_line_transforms = logical_line_transforms
570 self.logical_line_transforms = logical_line_transforms
566 else:
571 else:
567 self.logical_line_transforms = [
572 self.logical_line_transforms = [
568 help_end(),
573 help_end(),
569 escaped_commands(),
574 escaped_commands(),
570 assign_from_magic(),
575 assign_from_magic(),
571 assign_from_system(),
576 assign_from_system(),
572 ]
577 ]
573
578
574 self.assemble_python_lines = assemble_python_lines()
579 self.assemble_python_lines = assemble_python_lines()
575 if python_line_transforms is not None:
580 if python_line_transforms is not None:
576 self.python_line_transforms = python_line_transforms
581 self.python_line_transforms = python_line_transforms
577 else:
582 else:
578 # We don't use any of these at present
583 # We don't use any of these at present
579 self.python_line_transforms = []
584 self.python_line_transforms = []
580
585
581 @property
586 @property
582 def transforms(self):
587 def transforms(self):
583 "Quick access to all transformers."
588 "Quick access to all transformers."
584 return self.physical_line_transforms + \
589 return self.physical_line_transforms + \
585 [self.assemble_logical_lines] + self.logical_line_transforms + \
590 [self.assemble_logical_lines] + self.logical_line_transforms + \
586 [self.assemble_python_lines] + self.python_line_transforms
591 [self.assemble_python_lines] + self.python_line_transforms
587
592
588 @property
593 @property
589 def transforms_in_use(self):
594 def transforms_in_use(self):
590 """Transformers, excluding logical line transformers if we're in a
595 """Transformers, excluding logical line transformers if we're in a
591 Python line."""
596 Python line."""
592 t = self.physical_line_transforms[:]
597 t = self.physical_line_transforms[:]
593 if not self.within_python_line:
598 if not self.within_python_line:
594 t += [self.assemble_logical_lines] + self.logical_line_transforms
599 t += [self.assemble_logical_lines] + self.logical_line_transforms
595 return t + [self.assemble_python_lines] + self.python_line_transforms
600 return t + [self.assemble_python_lines] + self.python_line_transforms
596
601
597 def reset(self):
602 def reset(self):
598 """Reset the input buffer and associated state."""
603 """Reset the input buffer and associated state."""
599 super(IPythonInputSplitter, self).reset()
604 super(IPythonInputSplitter, self).reset()
600 self._buffer_raw[:] = []
605 self._buffer_raw[:] = []
601 self.source_raw = ''
606 self.source_raw = ''
602 self.transformer_accumulating = False
607 self.transformer_accumulating = False
603 self.within_python_line = False
608 self.within_python_line = False
604
609
605 for t in self.transforms:
610 for t in self.transforms:
606 try:
611 try:
607 t.reset()
612 t.reset()
608 except SyntaxError:
613 except SyntaxError:
609 # Nothing that calls reset() expects to handle transformer
614 # Nothing that calls reset() expects to handle transformer
610 # errors
615 # errors
611 pass
616 pass
612
617
613 def flush_transformers(self):
618 def flush_transformers(self):
614 def _flush(transform, outs):
619 def _flush(transform, outs):
615 """yield transformed lines
620 """yield transformed lines
616
621
617 always strings, never None
622 always strings, never None
618
623
619 transform: the current transform
624 transform: the current transform
620 outs: an iterable of previously transformed inputs.
625 outs: an iterable of previously transformed inputs.
621 Each may be multiline, which will be passed
626 Each may be multiline, which will be passed
622 one line at a time to transform.
627 one line at a time to transform.
623 """
628 """
624 for out in outs:
629 for out in outs:
625 for line in out.splitlines():
630 for line in out.splitlines():
626 # push one line at a time
631 # push one line at a time
627 tmp = transform.push(line)
632 tmp = transform.push(line)
628 if tmp is not None:
633 if tmp is not None:
629 yield tmp
634 yield tmp
630
635
631 # reset the transform
636 # reset the transform
632 tmp = transform.reset()
637 tmp = transform.reset()
633 if tmp is not None:
638 if tmp is not None:
634 yield tmp
639 yield tmp
635
640
636 out = []
641 out = []
637 for t in self.transforms_in_use:
642 for t in self.transforms_in_use:
638 out = _flush(t, out)
643 out = _flush(t, out)
639
644
640 out = list(out)
645 out = list(out)
641 if out:
646 if out:
642 self._store('\n'.join(out))
647 self._store('\n'.join(out))
643
648
644 def raw_reset(self):
649 def raw_reset(self):
645 """Return raw input only and perform a full reset.
650 """Return raw input only and perform a full reset.
646 """
651 """
647 out = self.source_raw
652 out = self.source_raw
648 self.reset()
653 self.reset()
649 return out
654 return out
650
655
651 def source_reset(self):
656 def source_reset(self):
652 try:
657 try:
653 self.flush_transformers()
658 self.flush_transformers()
654 return self.source
659 return self.source
655 finally:
660 finally:
656 self.reset()
661 self.reset()
657
662
658 def push_accepts_more(self):
663 def push_accepts_more(self):
659 if self.transformer_accumulating:
664 if self.transformer_accumulating:
660 return True
665 return True
661 else:
666 else:
662 return super(IPythonInputSplitter, self).push_accepts_more()
667 return super(IPythonInputSplitter, self).push_accepts_more()
663
668
664 def transform_cell(self, cell):
669 def transform_cell(self, cell):
665 """Process and translate a cell of input.
670 """Process and translate a cell of input.
666 """
671 """
667 self.reset()
672 self.reset()
668 try:
673 try:
669 self.push(cell)
674 self.push(cell)
670 self.flush_transformers()
675 self.flush_transformers()
671 return self.source
676 return self.source
672 finally:
677 finally:
673 self.reset()
678 self.reset()
674
679
675 def push(self, lines):
680 def push(self, lines):
676 """Push one or more lines of IPython input.
681 """Push one or more lines of IPython input.
677
682
678 This stores the given lines and returns a status code indicating
683 This stores the given lines and returns a status code indicating
679 whether the code forms a complete Python block or not, after processing
684 whether the code forms a complete Python block or not, after processing
680 all input lines for special IPython syntax.
685 all input lines for special IPython syntax.
681
686
682 Any exceptions generated in compilation are swallowed, but if an
687 Any exceptions generated in compilation are swallowed, but if an
683 exception was produced, the method returns True.
688 exception was produced, the method returns True.
684
689
685 Parameters
690 Parameters
686 ----------
691 ----------
687 lines : string
692 lines : string
688 One or more lines of Python input.
693 One or more lines of Python input.
689
694
690 Returns
695 Returns
691 -------
696 -------
692 is_complete : boolean
697 is_complete : boolean
693 True if the current input source (the result of the current input
698 True if the current input source (the result of the current input
694 plus prior inputs) forms a complete Python execution block. Note that
699 plus prior inputs) forms a complete Python execution block. Note that
695 this value is also stored as a private attribute (_is_complete), so it
700 this value is also stored as a private attribute (_is_complete), so it
696 can be queried at any time.
701 can be queried at any time.
697 """
702 """
698
703
699 # We must ensure all input is pure unicode
704 # We must ensure all input is pure unicode
700 lines = cast_unicode(lines, self.encoding)
705 lines = cast_unicode(lines, self.encoding)
701 # ''.splitlines() --> [], but we need to push the empty line to transformers
706 # ''.splitlines() --> [], but we need to push the empty line to transformers
702 lines_list = lines.splitlines()
707 lines_list = lines.splitlines()
703 if not lines_list:
708 if not lines_list:
704 lines_list = ['']
709 lines_list = ['']
705
710
706 # Store raw source before applying any transformations to it. Note
711 # Store raw source before applying any transformations to it. Note
707 # that this must be done *after* the reset() call that would otherwise
712 # that this must be done *after* the reset() call that would otherwise
708 # flush the buffer.
713 # flush the buffer.
709 self._store(lines, self._buffer_raw, 'source_raw')
714 self._store(lines, self._buffer_raw, 'source_raw')
710
715
711 transformed_lines_list = []
716 transformed_lines_list = []
712 for line in lines_list:
717 for line in lines_list:
713 transformed = self._transform_line(line)
718 transformed = self._transform_line(line)
714 if transformed is not None:
719 if transformed is not None:
715 transformed_lines_list.append(transformed)
720 transformed_lines_list.append(transformed)
716
721
717 if transformed_lines_list:
722 if transformed_lines_list:
718 transformed_lines = '\n'.join(transformed_lines_list)
723 transformed_lines = '\n'.join(transformed_lines_list)
719 return super(IPythonInputSplitter, self).push(transformed_lines)
724 return super(IPythonInputSplitter, self).push(transformed_lines)
720 else:
725 else:
721 # Got nothing back from transformers - they must be waiting for
726 # Got nothing back from transformers - they must be waiting for
722 # more input.
727 # more input.
723 return False
728 return False
724
729
725 def _transform_line(self, line):
730 def _transform_line(self, line):
726 """Push a line of input code through the various transformers.
731 """Push a line of input code through the various transformers.
727
732
728 Returns any output from the transformers, or None if a transformer
733 Returns any output from the transformers, or None if a transformer
729 is accumulating lines.
734 is accumulating lines.
730
735
731 Sets self.transformer_accumulating as a side effect.
736 Sets self.transformer_accumulating as a side effect.
732 """
737 """
733 def _accumulating(dbg):
738 def _accumulating(dbg):
734 #print(dbg)
739 #print(dbg)
735 self.transformer_accumulating = True
740 self.transformer_accumulating = True
736 return None
741 return None
737
742
738 for transformer in self.physical_line_transforms:
743 for transformer in self.physical_line_transforms:
739 line = transformer.push(line)
744 line = transformer.push(line)
740 if line is None:
745 if line is None:
741 return _accumulating(transformer)
746 return _accumulating(transformer)
742
747
743 if not self.within_python_line:
748 if not self.within_python_line:
744 line = self.assemble_logical_lines.push(line)
749 line = self.assemble_logical_lines.push(line)
745 if line is None:
750 if line is None:
746 return _accumulating('acc logical line')
751 return _accumulating('acc logical line')
747
752
748 for transformer in self.logical_line_transforms:
753 for transformer in self.logical_line_transforms:
749 line = transformer.push(line)
754 line = transformer.push(line)
750 if line is None:
755 if line is None:
751 return _accumulating(transformer)
756 return _accumulating(transformer)
752
757
753 line = self.assemble_python_lines.push(line)
758 line = self.assemble_python_lines.push(line)
754 if line is None:
759 if line is None:
755 self.within_python_line = True
760 self.within_python_line = True
756 return _accumulating('acc python line')
761 return _accumulating('acc python line')
757 else:
762 else:
758 self.within_python_line = False
763 self.within_python_line = False
759
764
760 for transformer in self.python_line_transforms:
765 for transformer in self.python_line_transforms:
761 line = transformer.push(line)
766 line = transformer.push(line)
762 if line is None:
767 if line is None:
763 return _accumulating(transformer)
768 return _accumulating(transformer)
764
769
765 #print("transformers clear") #debug
770 #print("transformers clear") #debug
766 self.transformer_accumulating = False
771 self.transformer_accumulating = False
767 return line
772 return line
768
773
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,189 +1,195 b''
1 """Tests for the token-based transformers in IPython.core.inputtransformer2
2
3 Line-based transformers are the simpler ones; token-based transformers are
4 more complex.
5 """
6
1 import nose.tools as nt
7 import nose.tools as nt
2
8
3 from IPython.core import inputtransformer2 as ipt2
9 from IPython.core import inputtransformer2 as ipt2
4 from IPython.core.inputtransformer2 import make_tokens_by_line
10 from IPython.core.inputtransformer2 import make_tokens_by_line
5
11
6 MULTILINE_MAGIC = ("""\
12 MULTILINE_MAGIC = ("""\
7 a = f()
13 a = f()
8 %foo \\
14 %foo \\
9 bar
15 bar
10 g()
16 g()
11 """.splitlines(keepends=True), (2, 0), """\
17 """.splitlines(keepends=True), (2, 0), """\
12 a = f()
18 a = f()
13 get_ipython().run_line_magic('foo', ' bar')
19 get_ipython().run_line_magic('foo', ' bar')
14 g()
20 g()
15 """.splitlines(keepends=True))
21 """.splitlines(keepends=True))
16
22
17 INDENTED_MAGIC = ("""\
23 INDENTED_MAGIC = ("""\
18 for a in range(5):
24 for a in range(5):
19 %ls
25 %ls
20 """.splitlines(keepends=True), (2, 4), """\
26 """.splitlines(keepends=True), (2, 4), """\
21 for a in range(5):
27 for a in range(5):
22 get_ipython().run_line_magic('ls', '')
28 get_ipython().run_line_magic('ls', '')
23 """.splitlines(keepends=True))
29 """.splitlines(keepends=True))
24
30
25 MULTILINE_MAGIC_ASSIGN = ("""\
31 MULTILINE_MAGIC_ASSIGN = ("""\
26 a = f()
32 a = f()
27 b = %foo \\
33 b = %foo \\
28 bar
34 bar
29 g()
35 g()
30 """.splitlines(keepends=True), (2, 4), """\
36 """.splitlines(keepends=True), (2, 4), """\
31 a = f()
37 a = f()
32 b = get_ipython().run_line_magic('foo', ' bar')
38 b = get_ipython().run_line_magic('foo', ' bar')
33 g()
39 g()
34 """.splitlines(keepends=True))
40 """.splitlines(keepends=True))
35
41
36 MULTILINE_SYSTEM_ASSIGN = ("""\
42 MULTILINE_SYSTEM_ASSIGN = ("""\
37 a = f()
43 a = f()
38 b = !foo \\
44 b = !foo \\
39 bar
45 bar
40 g()
46 g()
41 """.splitlines(keepends=True), (2, 4), """\
47 """.splitlines(keepends=True), (2, 4), """\
42 a = f()
48 a = f()
43 b = get_ipython().getoutput('foo bar')
49 b = get_ipython().getoutput('foo bar')
44 g()
50 g()
45 """.splitlines(keepends=True))
51 """.splitlines(keepends=True))
46
52
47 AUTOCALL_QUOTE = (
53 AUTOCALL_QUOTE = (
48 [",f 1 2 3\n"], (1, 0),
54 [",f 1 2 3\n"], (1, 0),
49 ['f("1", "2", "3")\n']
55 ['f("1", "2", "3")\n']
50 )
56 )
51
57
52 AUTOCALL_QUOTE2 = (
58 AUTOCALL_QUOTE2 = (
53 [";f 1 2 3\n"], (1, 0),
59 [";f 1 2 3\n"], (1, 0),
54 ['f("1 2 3")\n']
60 ['f("1 2 3")\n']
55 )
61 )
56
62
57 AUTOCALL_PAREN = (
63 AUTOCALL_PAREN = (
58 ["/f 1 2 3\n"], (1, 0),
64 ["/f 1 2 3\n"], (1, 0),
59 ['f(1, 2, 3)\n']
65 ['f(1, 2, 3)\n']
60 )
66 )
61
67
62 SIMPLE_HELP = (
68 SIMPLE_HELP = (
63 ["foo?\n"], (1, 0),
69 ["foo?\n"], (1, 0),
64 ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
70 ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
65 )
71 )
66
72
67 DETAILED_HELP = (
73 DETAILED_HELP = (
68 ["foo??\n"], (1, 0),
74 ["foo??\n"], (1, 0),
69 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
75 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
70 )
76 )
71
77
72 MAGIC_HELP = (
78 MAGIC_HELP = (
73 ["%foo?\n"], (1, 0),
79 ["%foo?\n"], (1, 0),
74 ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
80 ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
75 )
81 )
76
82
77 HELP_IN_EXPR = (
83 HELP_IN_EXPR = (
78 ["a = b + c?\n"], (1, 0),
84 ["a = b + c?\n"], (1, 0),
79 ["get_ipython().set_next_input('a = b + c');"
85 ["get_ipython().set_next_input('a = b + c');"
80 "get_ipython().run_line_magic('pinfo', 'c')\n"]
86 "get_ipython().run_line_magic('pinfo', 'c')\n"]
81 )
87 )
82
88
83 HELP_CONTINUED_LINE = ("""\
89 HELP_CONTINUED_LINE = ("""\
84 a = \\
90 a = \\
85 zip?
91 zip?
86 """.splitlines(keepends=True), (1, 0),
92 """.splitlines(keepends=True), (1, 0),
87 [r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
93 [r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
88 )
94 )
89
95
90 HELP_MULTILINE = ("""\
96 HELP_MULTILINE = ("""\
91 (a,
97 (a,
92 b) = zip?
98 b) = zip?
93 """.splitlines(keepends=True), (1, 0),
99 """.splitlines(keepends=True), (1, 0),
94 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
100 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
95 )
101 )
96
102
97 def check_find(transformer, case, match=True):
103 def check_find(transformer, case, match=True):
98 sample, expected_start, _ = case
104 sample, expected_start, _ = case
99 tbl = make_tokens_by_line(sample)
105 tbl = make_tokens_by_line(sample)
100 res = transformer.find(tbl)
106 res = transformer.find(tbl)
101 if match:
107 if match:
102 # start_line is stored 0-indexed, expected values are 1-indexed
108 # start_line is stored 0-indexed, expected values are 1-indexed
103 nt.assert_equal((res.start_line+1, res.start_col), expected_start)
109 nt.assert_equal((res.start_line+1, res.start_col), expected_start)
104 return res
110 return res
105 else:
111 else:
106 nt.assert_is(res, None)
112 nt.assert_is(res, None)
107
113
108 def check_transform(transformer_cls, case):
114 def check_transform(transformer_cls, case):
109 lines, start, expected = case
115 lines, start, expected = case
110 transformer = transformer_cls(start)
116 transformer = transformer_cls(start)
111 nt.assert_equal(transformer.transform(lines), expected)
117 nt.assert_equal(transformer.transform(lines), expected)
112
118
113 def test_continued_line():
119 def test_continued_line():
114 lines = MULTILINE_MAGIC_ASSIGN[0]
120 lines = MULTILINE_MAGIC_ASSIGN[0]
115 nt.assert_equal(ipt2.find_end_of_continued_line(lines, 1), 2)
121 nt.assert_equal(ipt2.find_end_of_continued_line(lines, 1), 2)
116
122
117 nt.assert_equal(ipt2.assemble_continued_line(lines, (1, 5), 2), "foo bar")
123 nt.assert_equal(ipt2.assemble_continued_line(lines, (1, 5), 2), "foo bar")
118
124
119 def test_find_assign_magic():
125 def test_find_assign_magic():
120 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
126 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
121 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
127 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
122
128
123 def test_transform_assign_magic():
129 def test_transform_assign_magic():
124 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
130 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
125
131
126 def test_find_assign_system():
132 def test_find_assign_system():
127 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
133 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
128 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
134 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
129 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
135 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
130 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
136 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
131
137
132 def test_transform_assign_system():
138 def test_transform_assign_system():
133 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
139 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
134
140
135 def test_find_magic_escape():
141 def test_find_magic_escape():
136 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
142 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
137 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
143 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
138 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
144 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
139
145
140 def test_transform_magic_escape():
146 def test_transform_magic_escape():
141 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
147 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
142 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
148 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
143
149
144 def test_find_autocalls():
150 def test_find_autocalls():
145 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
151 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
146 print("Testing %r" % case[0])
152 print("Testing %r" % case[0])
147 check_find(ipt2.EscapedCommand, case)
153 check_find(ipt2.EscapedCommand, case)
148
154
149 def test_transform_autocall():
155 def test_transform_autocall():
150 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
156 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
151 print("Testing %r" % case[0])
157 print("Testing %r" % case[0])
152 check_transform(ipt2.EscapedCommand, case)
158 check_transform(ipt2.EscapedCommand, case)
153
159
154 def test_find_help():
160 def test_find_help():
155 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
161 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
156 check_find(ipt2.HelpEnd, case)
162 check_find(ipt2.HelpEnd, case)
157
163
158 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
164 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
159 nt.assert_equal(tf.q_line, 1)
165 nt.assert_equal(tf.q_line, 1)
160 nt.assert_equal(tf.q_col, 3)
166 nt.assert_equal(tf.q_col, 3)
161
167
162 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
168 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
163 nt.assert_equal(tf.q_line, 1)
169 nt.assert_equal(tf.q_line, 1)
164 nt.assert_equal(tf.q_col, 8)
170 nt.assert_equal(tf.q_col, 8)
165
171
166 # ? in a comment does not trigger help
172 # ? in a comment does not trigger help
167 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
173 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
168 # Nor in a string
174 # Nor in a string
169 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
175 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
170
176
171 def test_transform_help():
177 def test_transform_help():
172 tf = ipt2.HelpEnd((1, 0), (1, 9))
178 tf = ipt2.HelpEnd((1, 0), (1, 9))
173 nt.assert_equal(tf.transform(HELP_IN_EXPR[0]), HELP_IN_EXPR[2])
179 nt.assert_equal(tf.transform(HELP_IN_EXPR[0]), HELP_IN_EXPR[2])
174
180
175 tf = ipt2.HelpEnd((1, 0), (2, 3))
181 tf = ipt2.HelpEnd((1, 0), (2, 3))
176 nt.assert_equal(tf.transform(HELP_CONTINUED_LINE[0]), HELP_CONTINUED_LINE[2])
182 nt.assert_equal(tf.transform(HELP_CONTINUED_LINE[0]), HELP_CONTINUED_LINE[2])
177
183
178 tf = ipt2.HelpEnd((1, 0), (2, 8))
184 tf = ipt2.HelpEnd((1, 0), (2, 8))
179 nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2])
185 nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2])
180
186
181 def test_check_complete():
187 def test_check_complete():
182 cc = ipt2.TransformerManager().check_complete
188 cc = ipt2.TransformerManager().check_complete
183 nt.assert_equal(cc("a = 1"), ('complete', None))
189 nt.assert_equal(cc("a = 1"), ('complete', None))
184 nt.assert_equal(cc("for a in range(5):"), ('incomplete', 4))
190 nt.assert_equal(cc("for a in range(5):"), ('incomplete', 4))
185 nt.assert_equal(cc("raise = 2"), ('invalid', None))
191 nt.assert_equal(cc("raise = 2"), ('invalid', None))
186 nt.assert_equal(cc("a = [1,\n2,"), ('incomplete', 0))
192 nt.assert_equal(cc("a = [1,\n2,"), ('incomplete', 0))
187 nt.assert_equal(cc("a = '''\n hi"), ('incomplete', 3))
193 nt.assert_equal(cc("a = '''\n hi"), ('incomplete', 3))
188 nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None))
194 nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None))
189 nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash
195 nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash
General Comments 0
You need to be logged in to leave comments. Login now