##// END OF EJS Templates
First pass of input syntax transformation support
Fernando Perez -
Show More
@@ -1,421 +1,521 b''
1 """Analysis of text input into executable blocks.
1 """Analysis of text input into executable blocks.
2
2
3 The main class in this module, :class:`InputSplitter`, is designed to break
3 The main class in this module, :class:`InputSplitter`, is designed to break
4 input from either interactive, line-by-line environments or block-based ones,
4 input from either interactive, line-by-line environments or block-based ones,
5 into standalone blocks that can be executed by Python as 'single' statements
5 into standalone blocks that can be executed by Python as 'single' statements
6 (thus triggering sys.displayhook).
6 (thus triggering sys.displayhook).
7
7
8 For more details, see the class docstring below.
8 For more details, see the class docstring below.
9 """
9 """
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2010 The IPython Development Team
11 # Copyright (C) 2010 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # stdlib
20 # stdlib
21 import codeop
21 import codeop
22 import re
22 import re
23 import sys
23 import sys
24
24
25 # IPython modules
26 from IPython.utils.text import make_quoted_expr
27
25 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
26 # Utilities
29 # Utilities
27 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
28
31
29 # FIXME: move these utilities to the general ward...
32 # FIXME: move these utilities to the general ward...
30
33
31 # compiled regexps for autoindent management
34 # compiled regexps for autoindent management
32 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
35 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
33 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
36 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
34
37
35
38
36 def num_ini_spaces(s):
39 def num_ini_spaces(s):
37 """Return the number of initial spaces in a string.
40 """Return the number of initial spaces in a string.
38
41
39 Note that tabs are counted as a single space. For now, we do *not* support
42 Note that tabs are counted as a single space. For now, we do *not* support
40 mixing of tabs and spaces in the user's input.
43 mixing of tabs and spaces in the user's input.
41
44
42 Parameters
45 Parameters
43 ----------
46 ----------
44 s : string
47 s : string
45
48
46 Returns
49 Returns
47 -------
50 -------
48 n : int
51 n : int
49 """
52 """
50
53
51 ini_spaces = ini_spaces_re.match(s)
54 ini_spaces = ini_spaces_re.match(s)
52 if ini_spaces:
55 if ini_spaces:
53 return ini_spaces.end()
56 return ini_spaces.end()
54 else:
57 else:
55 return 0
58 return 0
56
59
57
60
58 def remove_comments(src):
61 def remove_comments(src):
59 """Remove all comments from input source.
62 """Remove all comments from input source.
60
63
61 Note: comments are NOT recognized inside of strings!
64 Note: comments are NOT recognized inside of strings!
62
65
63 Parameters
66 Parameters
64 ----------
67 ----------
65 src : string
68 src : string
66 A single or multiline input string.
69 A single or multiline input string.
67
70
68 Returns
71 Returns
69 -------
72 -------
70 String with all Python comments removed.
73 String with all Python comments removed.
71 """
74 """
72
75
73 return re.sub('#.*', '', src)
76 return re.sub('#.*', '', src)
74
77
75
78
76 def get_input_encoding():
79 def get_input_encoding():
77 """Return the default standard input encoding.
80 """Return the default standard input encoding.
78
81
79 If sys.stdin has no encoding, 'ascii' is returned."""
82 If sys.stdin has no encoding, 'ascii' is returned."""
80 # There are strange environments for which sys.stdin.encoding is None. We
83 # There are strange environments for which sys.stdin.encoding is None. We
81 # ensure that a valid encoding is returned.
84 # ensure that a valid encoding is returned.
82 encoding = getattr(sys.stdin, 'encoding', None)
85 encoding = getattr(sys.stdin, 'encoding', None)
83 if encoding is None:
86 if encoding is None:
84 encoding = 'ascii'
87 encoding = 'ascii'
85 return encoding
88 return encoding
86
89
87 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
88 # Classes and functions
91 # Classes and functions
89 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
90
93
91 class InputSplitter(object):
94 class InputSplitter(object):
92 """An object that can split Python source input in executable blocks.
95 """An object that can split Python source input in executable blocks.
93
96
94 This object is designed to be used in one of two basic modes:
97 This object is designed to be used in one of two basic modes:
95
98
96 1. By feeding it python source line-by-line, using :meth:`push`. In this
99 1. By feeding it python source line-by-line, using :meth:`push`. In this
97 mode, it will return on each push whether the currently pushed code
100 mode, it will return on each push whether the currently pushed code
98 could be executed already. In addition, it provides a method called
101 could be executed already. In addition, it provides a method called
99 :meth:`push_accepts_more` that can be used to query whether more input
102 :meth:`push_accepts_more` that can be used to query whether more input
100 can be pushed into a single interactive block.
103 can be pushed into a single interactive block.
101
104
102 2. By calling :meth:`split_blocks` with a single, multiline Python string,
105 2. By calling :meth:`split_blocks` with a single, multiline Python string,
103 that is then split into blocks each of which can be executed
106 that is then split into blocks each of which can be executed
104 interactively as a single statement.
107 interactively as a single statement.
105
108
106 This is a simple example of how an interactive terminal-based client can use
109 This is a simple example of how an interactive terminal-based client can use
107 this tool::
110 this tool::
108
111
109 isp = InputSplitter()
112 isp = InputSplitter()
110 while isp.push_accepts_more():
113 while isp.push_accepts_more():
111 indent = ' '*isp.indent_spaces
114 indent = ' '*isp.indent_spaces
112 prompt = '>>> ' + indent
115 prompt = '>>> ' + indent
113 line = indent + raw_input(prompt)
116 line = indent + raw_input(prompt)
114 isp.push(line)
117 isp.push(line)
115 print 'Input source was:\n', isp.source_reset(),
118 print 'Input source was:\n', isp.source_reset(),
116 """
119 """
117 # Number of spaces of indentation computed from input that has been pushed
120 # Number of spaces of indentation computed from input that has been pushed
118 # so far. This is the attributes callers should query to get the current
121 # so far. This is the attributes callers should query to get the current
119 # indentation level, in order to provide auto-indent facilities.
122 # indentation level, in order to provide auto-indent facilities.
120 indent_spaces = 0
123 indent_spaces = 0
121 # String, indicating the default input encoding. It is computed by default
124 # String, indicating the default input encoding. It is computed by default
122 # at initialization time via get_input_encoding(), but it can be reset by a
125 # at initialization time via get_input_encoding(), but it can be reset by a
123 # client with specific knowledge of the encoding.
126 # client with specific knowledge of the encoding.
124 encoding = ''
127 encoding = ''
125 # String where the current full source input is stored, properly encoded.
128 # String where the current full source input is stored, properly encoded.
126 # Reading this attribute is the normal way of querying the currently pushed
129 # Reading this attribute is the normal way of querying the currently pushed
127 # source code, that has been properly encoded.
130 # source code, that has been properly encoded.
128 source = ''
131 source = ''
129 # Code object corresponding to the current source. It is automatically
132 # Code object corresponding to the current source. It is automatically
130 # synced to the source, so it can be queried at any time to obtain the code
133 # synced to the source, so it can be queried at any time to obtain the code
131 # object; it will be None if the source doesn't compile to valid Python.
134 # object; it will be None if the source doesn't compile to valid Python.
132 code = None
135 code = None
133 # Input mode
136 # Input mode
134 input_mode = 'append'
137 input_mode = 'append'
135
138
136 # Private attributes
139 # Private attributes
137
140
138 # List with lines of input accumulated so far
141 # List with lines of input accumulated so far
139 _buffer = None
142 _buffer = None
140 # Command compiler
143 # Command compiler
141 _compile = None
144 _compile = None
142 # Mark when input has changed indentation all the way back to flush-left
145 # Mark when input has changed indentation all the way back to flush-left
143 _full_dedent = False
146 _full_dedent = False
144 # Boolean indicating whether the current block is complete
147 # Boolean indicating whether the current block is complete
145 _is_complete = None
148 _is_complete = None
146
149
147 def __init__(self, input_mode=None):
150 def __init__(self, input_mode=None):
148 """Create a new InputSplitter instance.
151 """Create a new InputSplitter instance.
149
152
150 Parameters
153 Parameters
151 ----------
154 ----------
152 input_mode : str
155 input_mode : str
153
156
154 One of 'append', 'replace', default is 'append'. This controls how
157 One of 'append', 'replace', default is 'append'. This controls how
155 new inputs are used: in 'append' mode, they are appended to the
158 new inputs are used: in 'append' mode, they are appended to the
156 existing buffer and the whole buffer is compiled; in 'replace' mode,
159 existing buffer and the whole buffer is compiled; in 'replace' mode,
157 each new input completely replaces all prior inputs. Replace mode is
160 each new input completely replaces all prior inputs. Replace mode is
158 thus equivalent to prepending a full reset() to every push() call.
161 thus equivalent to prepending a full reset() to every push() call.
159
162
160 In practice, line-oriented clients likely want to use 'append' mode
163 In practice, line-oriented clients likely want to use 'append' mode
161 while block-oriented ones will want to use 'replace'.
164 while block-oriented ones will want to use 'replace'.
162 """
165 """
163 self._buffer = []
166 self._buffer = []
164 self._compile = codeop.CommandCompiler()
167 self._compile = codeop.CommandCompiler()
165 self.encoding = get_input_encoding()
168 self.encoding = get_input_encoding()
166 self.input_mode = InputSplitter.input_mode if input_mode is None \
169 self.input_mode = InputSplitter.input_mode if input_mode is None \
167 else input_mode
170 else input_mode
168
171
169 def reset(self):
172 def reset(self):
170 """Reset the input buffer and associated state."""
173 """Reset the input buffer and associated state."""
171 self.indent_spaces = 0
174 self.indent_spaces = 0
172 self._buffer[:] = []
175 self._buffer[:] = []
173 self.source = ''
176 self.source = ''
174 self.code = None
177 self.code = None
175 self._is_complete = False
178 self._is_complete = False
176 self._full_dedent = False
179 self._full_dedent = False
177
180
178 def source_reset(self):
181 def source_reset(self):
179 """Return the input source and perform a full reset.
182 """Return the input source and perform a full reset.
180 """
183 """
181 out = self.source
184 out = self.source
182 self.reset()
185 self.reset()
183 return out
186 return out
184
187
185 def push(self, lines):
188 def push(self, lines):
186 """Push one ore more lines of input.
189 """Push one ore more lines of input.
187
190
188 This stores the given lines and returns a status code indicating
191 This stores the given lines and returns a status code indicating
189 whether the code forms a complete Python block or not.
192 whether the code forms a complete Python block or not.
190
193
191 Any exceptions generated in compilation are swallowed, but if an
194 Any exceptions generated in compilation are swallowed, but if an
192 exception was produced, the method returns True.
195 exception was produced, the method returns True.
193
196
194 Parameters
197 Parameters
195 ----------
198 ----------
196 lines : string
199 lines : string
197 One or more lines of Python input.
200 One or more lines of Python input.
198
201
199 Returns
202 Returns
200 -------
203 -------
201 is_complete : boolean
204 is_complete : boolean
202 True if the current input source (the result of the current input
205 True if the current input source (the result of the current input
203 plus prior inputs) forms a complete Python execution block. Note that
206 plus prior inputs) forms a complete Python execution block. Note that
204 this value is also stored as a private attribute (_is_complete), so it
207 this value is also stored as a private attribute (_is_complete), so it
205 can be queried at any time.
208 can be queried at any time.
206 """
209 """
207 if self.input_mode == 'replace':
210 if self.input_mode == 'replace':
208 self.reset()
211 self.reset()
209
212
210 # If the source code has leading blanks, add 'if 1:\n' to it
213 # If the source code has leading blanks, add 'if 1:\n' to it
211 # this allows execution of indented pasted code. It is tempting
214 # this allows execution of indented pasted code. It is tempting
212 # to add '\n' at the end of source to run commands like ' a=1'
215 # to add '\n' at the end of source to run commands like ' a=1'
213 # directly, but this fails for more complicated scenarios
216 # directly, but this fails for more complicated scenarios
214 if not self._buffer and lines[:1] in [' ', '\t']:
217 if not self._buffer and lines[:1] in [' ', '\t']:
215 lines = 'if 1:\n%s' % lines
218 lines = 'if 1:\n%s' % lines
216
219
217 self._store(lines)
220 self._store(lines)
218 source = self.source
221 source = self.source
219
222
220 # Before calling _compile(), reset the code object to None so that if an
223 # Before calling _compile(), reset the code object to None so that if an
221 # exception is raised in compilation, we don't mislead by having
224 # exception is raised in compilation, we don't mislead by having
222 # inconsistent code/source attributes.
225 # inconsistent code/source attributes.
223 self.code, self._is_complete = None, None
226 self.code, self._is_complete = None, None
224
227
225 self._update_indent(lines)
228 self._update_indent(lines)
226 try:
229 try:
227 self.code = self._compile(source)
230 self.code = self._compile(source)
228 # Invalid syntax can produce any of a number of different errors from
231 # Invalid syntax can produce any of a number of different errors from
229 # inside the compiler, so we have to catch them all. Syntax errors
232 # inside the compiler, so we have to catch them all. Syntax errors
230 # immediately produce a 'ready' block, so the invalid Python can be
233 # immediately produce a 'ready' block, so the invalid Python can be
231 # sent to the kernel for evaluation with possible ipython
234 # sent to the kernel for evaluation with possible ipython
232 # special-syntax conversion.
235 # special-syntax conversion.
233 except (SyntaxError, OverflowError, ValueError, TypeError,
236 except (SyntaxError, OverflowError, ValueError, TypeError,
234 MemoryError):
237 MemoryError):
235 self._is_complete = True
238 self._is_complete = True
236 else:
239 else:
237 # Compilation didn't produce any exceptions (though it may not have
240 # Compilation didn't produce any exceptions (though it may not have
238 # given a complete code object)
241 # given a complete code object)
239 self._is_complete = self.code is not None
242 self._is_complete = self.code is not None
240
243
241 return self._is_complete
244 return self._is_complete
242
245
243 def push_accepts_more(self):
246 def push_accepts_more(self):
244 """Return whether a block of interactive input can accept more input.
247 """Return whether a block of interactive input can accept more input.
245
248
246 This method is meant to be used by line-oriented frontends, who need to
249 This method is meant to be used by line-oriented frontends, who need to
247 guess whether a block is complete or not based solely on prior and
250 guess whether a block is complete or not based solely on prior and
248 current input lines. The InputSplitter considers it has a complete
251 current input lines. The InputSplitter considers it has a complete
249 interactive block and will not accept more input only when either a
252 interactive block and will not accept more input only when either a
250 SyntaxError is raised, or *all* of the following are true:
253 SyntaxError is raised, or *all* of the following are true:
251
254
252 1. The input compiles to a complete statement.
255 1. The input compiles to a complete statement.
253
256
254 2. The indentation level is flush-left (because if we are indented,
257 2. The indentation level is flush-left (because if we are indented,
255 like inside a function definition or for loop, we need to keep
258 like inside a function definition or for loop, we need to keep
256 reading new input).
259 reading new input).
257
260
258 3. There is one extra line consisting only of whitespace.
261 3. There is one extra line consisting only of whitespace.
259
262
260 Because of condition #3, this method should be used only by
263 Because of condition #3, this method should be used only by
261 *line-oriented* frontends, since it means that intermediate blank lines
264 *line-oriented* frontends, since it means that intermediate blank lines
262 are not allowed in function definitions (or any other indented block).
265 are not allowed in function definitions (or any other indented block).
263
266
264 Block-oriented frontends that have a separate keyboard event to
267 Block-oriented frontends that have a separate keyboard event to
265 indicate execution should use the :meth:`split_blocks` method instead.
268 indicate execution should use the :meth:`split_blocks` method instead.
266
269
267 If the current input produces a syntax error, this method immediately
270 If the current input produces a syntax error, this method immediately
268 returns False but does *not* raise the syntax error exception, as
271 returns False but does *not* raise the syntax error exception, as
269 typically clients will want to send invalid syntax to an execution
272 typically clients will want to send invalid syntax to an execution
270 backend which might convert the invalid syntax into valid Python via
273 backend which might convert the invalid syntax into valid Python via
271 one of the dynamic IPython mechanisms.
274 one of the dynamic IPython mechanisms.
272 """
275 """
273
276
274 if not self._is_complete:
277 if not self._is_complete:
275 return True
278 return True
276
279
277 if self.indent_spaces==0:
280 if self.indent_spaces==0:
278 return False
281 return False
279
282
280 last_line = self.source.splitlines()[-1]
283 last_line = self.source.splitlines()[-1]
281 return bool(last_line and not last_line.isspace())
284 return bool(last_line and not last_line.isspace())
282
285
283 def split_blocks(self, lines):
286 def split_blocks(self, lines):
284 """Split a multiline string into multiple input blocks.
287 """Split a multiline string into multiple input blocks.
285
288
286 Note: this method starts by performing a full reset().
289 Note: this method starts by performing a full reset().
287
290
288 Parameters
291 Parameters
289 ----------
292 ----------
290 lines : str
293 lines : str
291 A possibly multiline string.
294 A possibly multiline string.
292
295
293 Returns
296 Returns
294 -------
297 -------
295 blocks : list
298 blocks : list
296 A list of strings, each possibly multiline. Each string corresponds
299 A list of strings, each possibly multiline. Each string corresponds
297 to a single block that can be compiled in 'single' mode (unless it
300 to a single block that can be compiled in 'single' mode (unless it
298 has a syntax error)."""
301 has a syntax error)."""
299
302
300 # This code is fairly delicate. If you make any changes here, make
303 # This code is fairly delicate. If you make any changes here, make
301 # absolutely sure that you do run the full test suite and ALL tests
304 # absolutely sure that you do run the full test suite and ALL tests
302 # pass.
305 # pass.
303
306
304 self.reset()
307 self.reset()
305 blocks = []
308 blocks = []
306
309
307 # Reversed copy so we can use pop() efficiently and consume the input
310 # Reversed copy so we can use pop() efficiently and consume the input
308 # as a stack
311 # as a stack
309 lines = lines.splitlines()[::-1]
312 lines = lines.splitlines()[::-1]
310 # Outer loop over all input
313 # Outer loop over all input
311 while lines:
314 while lines:
312 # Inner loop to build each block
315 # Inner loop to build each block
313 while True:
316 while True:
314 # Safety exit from inner loop
317 # Safety exit from inner loop
315 if not lines:
318 if not lines:
316 break
319 break
317 # Grab next line but don't push it yet
320 # Grab next line but don't push it yet
318 next_line = lines.pop()
321 next_line = lines.pop()
319 # Blank/empty lines are pushed as-is
322 # Blank/empty lines are pushed as-is
320 if not next_line or next_line.isspace():
323 if not next_line or next_line.isspace():
321 self.push(next_line)
324 self.push(next_line)
322 continue
325 continue
323
326
324 # Check indentation changes caused by the *next* line
327 # Check indentation changes caused by the *next* line
325 indent_spaces, _full_dedent = self._find_indent(next_line)
328 indent_spaces, _full_dedent = self._find_indent(next_line)
326
329
327 # If the next line causes a dedent, it can be for two differnt
330 # If the next line causes a dedent, it can be for two differnt
328 # reasons: either an explicit de-dent by the user or a
331 # reasons: either an explicit de-dent by the user or a
329 # return/raise/pass statement. These MUST be handled
332 # return/raise/pass statement. These MUST be handled
330 # separately:
333 # separately:
331 #
334 #
332 # 1. the first case is only detected when the actual explicit
335 # 1. the first case is only detected when the actual explicit
333 # dedent happens, and that would be the *first* line of a *new*
336 # dedent happens, and that would be the *first* line of a *new*
334 # block. Thus, we must put the line back into the input buffer
337 # block. Thus, we must put the line back into the input buffer
335 # so that it starts a new block on the next pass.
338 # so that it starts a new block on the next pass.
336 #
339 #
337 # 2. the second case is detected in the line before the actual
340 # 2. the second case is detected in the line before the actual
338 # dedent happens, so , we consume the line and we can break out
341 # dedent happens, so , we consume the line and we can break out
339 # to start a new block.
342 # to start a new block.
340
343
341 # Case 1, explicit dedent causes a break
344 # Case 1, explicit dedent causes a break
342 if _full_dedent and not next_line.startswith(' '):
345 if _full_dedent and not next_line.startswith(' '):
343 lines.append(next_line)
346 lines.append(next_line)
344 break
347 break
345
348
346 # Otherwise any line is pushed
349 # Otherwise any line is pushed
347 self.push(next_line)
350 self.push(next_line)
348
351
349 # Case 2, full dedent with full block ready:
352 # Case 2, full dedent with full block ready:
350 if _full_dedent or \
353 if _full_dedent or \
351 self.indent_spaces==0 and not self.push_accepts_more():
354 self.indent_spaces==0 and not self.push_accepts_more():
352 break
355 break
353 # Form the new block with the current source input
356 # Form the new block with the current source input
354 blocks.append(self.source_reset())
357 blocks.append(self.source_reset())
355
358
356 return blocks
359 return blocks
357
360
358 #------------------------------------------------------------------------
361 #------------------------------------------------------------------------
359 # Private interface
362 # Private interface
360 #------------------------------------------------------------------------
363 #------------------------------------------------------------------------
361
364
362 def _find_indent(self, line):
365 def _find_indent(self, line):
363 """Compute the new indentation level for a single line.
366 """Compute the new indentation level for a single line.
364
367
365 Parameters
368 Parameters
366 ----------
369 ----------
367 line : str
370 line : str
368 A single new line of non-whitespace, non-comment Python input.
371 A single new line of non-whitespace, non-comment Python input.
369
372
370 Returns
373 Returns
371 -------
374 -------
372 indent_spaces : int
375 indent_spaces : int
373 New value for the indent level (it may be equal to self.indent_spaces
376 New value for the indent level (it may be equal to self.indent_spaces
374 if indentation doesn't change.
377 if indentation doesn't change.
375
378
376 full_dedent : boolean
379 full_dedent : boolean
377 Whether the new line causes a full flush-left dedent.
380 Whether the new line causes a full flush-left dedent.
378 """
381 """
379 indent_spaces = self.indent_spaces
382 indent_spaces = self.indent_spaces
380 full_dedent = self._full_dedent
383 full_dedent = self._full_dedent
381
384
382 inisp = num_ini_spaces(line)
385 inisp = num_ini_spaces(line)
383 if inisp < indent_spaces:
386 if inisp < indent_spaces:
384 indent_spaces = inisp
387 indent_spaces = inisp
385 if indent_spaces <= 0:
388 if indent_spaces <= 0:
386 #print 'Full dedent in text',self.source # dbg
389 #print 'Full dedent in text',self.source # dbg
387 full_dedent = True
390 full_dedent = True
388
391
389 if line[-1] == ':':
392 if line[-1] == ':':
390 indent_spaces += 4
393 indent_spaces += 4
391 elif dedent_re.match(line):
394 elif dedent_re.match(line):
392 indent_spaces -= 4
395 indent_spaces -= 4
393 if indent_spaces <= 0:
396 if indent_spaces <= 0:
394 full_dedent = True
397 full_dedent = True
395
398
396 # Safety
399 # Safety
397 if indent_spaces < 0:
400 if indent_spaces < 0:
398 indent_spaces = 0
401 indent_spaces = 0
399 #print 'safety' # dbg
402 #print 'safety' # dbg
400
403
401 return indent_spaces, full_dedent
404 return indent_spaces, full_dedent
402
405
403 def _update_indent(self, lines):
406 def _update_indent(self, lines):
404 for line in remove_comments(lines).splitlines():
407 for line in remove_comments(lines).splitlines():
405 if line and not line.isspace():
408 if line and not line.isspace():
406 self.indent_spaces, self._full_dedent = self._find_indent(line)
409 self.indent_spaces, self._full_dedent = self._find_indent(line)
407
410
408 def _store(self, lines):
411 def _store(self, lines):
409 """Store one or more lines of input.
412 """Store one or more lines of input.
410
413
411 If input lines are not newline-terminated, a newline is automatically
414 If input lines are not newline-terminated, a newline is automatically
412 appended."""
415 appended."""
413
416
414 if lines.endswith('\n'):
417 if lines.endswith('\n'):
415 self._buffer.append(lines)
418 self._buffer.append(lines)
416 else:
419 else:
417 self._buffer.append(lines+'\n')
420 self._buffer.append(lines+'\n')
418 self._set_source()
421 self._set_source()
419
422
420 def _set_source(self):
423 def _set_source(self):
421 self.source = ''.join(self._buffer).encode(self.encoding)
424 self.source = ''.join(self._buffer).encode(self.encoding)
425
426
427 #-----------------------------------------------------------------------------
428 # IPython-specific syntactic support
429 #-----------------------------------------------------------------------------
430
431 # We implement things, as much as possible, as standalone functions that can be
432 # tested and validated in isolation.
433
434 # Each of these uses a regexp, we pre-compile these and keep them close to each
435 # function definition for clarity
436 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
437 r'\s*=\s*!\s*(?P<cmd>.*)')
438
439 def transform_assign_system(line):
440 """Handle the `files = !ls` syntax."""
441 # FIXME: This transforms the line to use %sc, but we've listed that magic
442 # as deprecated. We should then implement this functionality in a
443 # standalone api that we can transform to, without going through a
444 # deprecated magic.
445 m = _assign_system_re.match(line)
446 if m is not None:
447 cmd = m.group('cmd')
448 lhs = m.group('lhs')
449 expr = make_quoted_expr("sc -l = %s" % cmd)
450 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
451 return new_line
452 return line
453
454
455 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
456 r'\s*=\s*%\s*(?P<cmd>.*)')
457
458 def transform_assign_magic(line):
459 """Handle the `a = %who` syntax."""
460 m = _assign_magic_re.match(line)
461 if m is not None:
462 cmd = m.group('cmd')
463 lhs = m.group('lhs')
464 expr = make_quoted_expr(cmd)
465 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
466 return new_line
467 return line
468
469
470 _classic_prompt_re = re.compile(r'(^[ \t]*>>> |^[ \t]*\.\.\. )')
471
472 def transform_classic_prompt(line):
473 """Handle inputs that start with '>>> ' syntax."""
474
475 if not line or line.isspace() or line.strip() == '...':
476 # This allows us to recognize multiple input prompts separated by
477 # blank lines and pasted in a single chunk, very common when
478 # pasting doctests or long tutorial passages.
479 return ''
480 m = _classic_prompt_re.match(line)
481 if m:
482 return line[len(m.group(0)):]
483 else:
484 return line
485
486
487 _ipy_prompt_re = re.compile(r'(^[ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
488
489 def transform_ipy_prompt(line):
490 """Handle inputs that start classic IPython prompt syntax."""
491
492 if not line or line.isspace() or line.strip() == '...':
493 # This allows us to recognize multiple input prompts separated by
494 # blank lines and pasted in a single chunk, very common when
495 # pasting doctests or long tutorial passages.
496 return ''
497 m = _ipy_prompt_re.match(line)
498 if m:
499 return line[len(m.group(0)):]
500 else:
501 return line
502
503
504 # Warning, these cannot be changed unless various regular expressions
505 # are updated in a number of places. Not great, but at least we told you.
506 ESC_SHELL = '!'
507 ESC_SH_CAP = '!!'
508 ESC_HELP = '?'
509 ESC_MAGIC = '%'
510 ESC_QUOTE = ','
511 ESC_QUOTE2 = ';'
512 ESC_PAREN = '/'
513
514 class IPythonInputSplitter(InputSplitter):
515 """An input splitter that recognizes all of IPython's special syntax."""
516
517
518 def push(self, lines):
519 """Push one or more lines of IPython input.
520 """
521 return super(IPythonInputSplitter, self).push(lines)
@@ -1,364 +1,411 b''
1 """Tests for the inputsplitter module.
1 """Tests for the inputsplitter module.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010 The IPython Development Team
4 # Copyright (C) 2010 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # stdlib
13 # stdlib
14 import unittest
14 import unittest
15 import sys
15 import sys
16
16
17 # Third party
17 # Third party
18 import nose.tools as nt
18 import nose.tools as nt
19
19
20 # Our own
20 # Our own
21 from IPython.core import inputsplitter as isp
21 from IPython.core import inputsplitter as isp
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Semi-complete examples (also used as tests)
24 # Semi-complete examples (also used as tests)
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 def mini_interactive_loop(raw_input):
26 def mini_interactive_loop(raw_input):
27 """Minimal example of the logic of an interactive interpreter loop.
27 """Minimal example of the logic of an interactive interpreter loop.
28
28
29 This serves as an example, and it is used by the test system with a fake
29 This serves as an example, and it is used by the test system with a fake
30 raw_input that simulates interactive input."""
30 raw_input that simulates interactive input."""
31
31
32 from IPython.core.inputsplitter import InputSplitter
32 from IPython.core.inputsplitter import InputSplitter
33
33
34 isp = InputSplitter()
34 isp = InputSplitter()
35 # In practice, this input loop would be wrapped in an outside loop to read
35 # In practice, this input loop would be wrapped in an outside loop to read
36 # input indefinitely, until some exit/quit command was issued. Here we
36 # input indefinitely, until some exit/quit command was issued. Here we
37 # only illustrate the basic inner loop.
37 # only illustrate the basic inner loop.
38 while isp.push_accepts_more():
38 while isp.push_accepts_more():
39 indent = ' '*isp.indent_spaces
39 indent = ' '*isp.indent_spaces
40 prompt = '>>> ' + indent
40 prompt = '>>> ' + indent
41 line = indent + raw_input(prompt)
41 line = indent + raw_input(prompt)
42 isp.push(line)
42 isp.push(line)
43
43
44 # Here we just return input so we can use it in a test suite, but a real
44 # Here we just return input so we can use it in a test suite, but a real
45 # interpreter would instead send it for execution somewhere.
45 # interpreter would instead send it for execution somewhere.
46 src = isp.source_reset()
46 src = isp.source_reset()
47 print 'Input source was:\n', src
47 print 'Input source was:\n', src
48 return src
48 return src
49
49
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 # Test utilities, just for local use
51 # Test utilities, just for local use
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53
53
54 def assemble(block):
54 def assemble(block):
55 """Assemble a block into multi-line sub-blocks."""
55 """Assemble a block into multi-line sub-blocks."""
56 return ['\n'.join(sub_block)+'\n' for sub_block in block]
56 return ['\n'.join(sub_block)+'\n' for sub_block in block]
57
57
58
58
59 def pseudo_input(lines):
59 def pseudo_input(lines):
60 """Return a function that acts like raw_input but feeds the input list."""
60 """Return a function that acts like raw_input but feeds the input list."""
61 ilines = iter(lines)
61 ilines = iter(lines)
62 def raw_in(prompt):
62 def raw_in(prompt):
63 try:
63 try:
64 return next(ilines)
64 return next(ilines)
65 except StopIteration:
65 except StopIteration:
66 return ''
66 return ''
67 return raw_in
67 return raw_in
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Tests
70 # Tests
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72 def test_spaces():
72 def test_spaces():
73 tests = [('', 0),
73 tests = [('', 0),
74 (' ', 1),
74 (' ', 1),
75 ('\n', 0),
75 ('\n', 0),
76 (' \n', 1),
76 (' \n', 1),
77 ('x', 0),
77 ('x', 0),
78 (' x', 1),
78 (' x', 1),
79 (' x',2),
79 (' x',2),
80 (' x',4),
80 (' x',4),
81 # Note: tabs are counted as a single whitespace!
81 # Note: tabs are counted as a single whitespace!
82 ('\tx', 1),
82 ('\tx', 1),
83 ('\t x', 2),
83 ('\t x', 2),
84 ]
84 ]
85
85
86 for s, nsp in tests:
86 for s, nsp in tests:
87 nt.assert_equal(isp.num_ini_spaces(s), nsp)
87 nt.assert_equal(isp.num_ini_spaces(s), nsp)
88
88
89
89
90 def test_remove_comments():
90 def test_remove_comments():
91 tests = [('text', 'text'),
91 tests = [('text', 'text'),
92 ('text # comment', 'text '),
92 ('text # comment', 'text '),
93 ('text # comment\n', 'text \n'),
93 ('text # comment\n', 'text \n'),
94 ('text # comment \n', 'text \n'),
94 ('text # comment \n', 'text \n'),
95 ('line # c \nline\n','line \nline\n'),
95 ('line # c \nline\n','line \nline\n'),
96 ('line # c \nline#c2 \nline\nline #c\n\n',
96 ('line # c \nline#c2 \nline\nline #c\n\n',
97 'line \nline\nline\nline \n\n'),
97 'line \nline\nline\nline \n\n'),
98 ]
98 ]
99
99
100 for inp, out in tests:
100 for inp, out in tests:
101 nt.assert_equal(isp.remove_comments(inp), out)
101 nt.assert_equal(isp.remove_comments(inp), out)
102
102
103
103
104 def test_get_input_encoding():
104 def test_get_input_encoding():
105 encoding = isp.get_input_encoding()
105 encoding = isp.get_input_encoding()
106 nt.assert_true(isinstance(encoding, basestring))
106 nt.assert_true(isinstance(encoding, basestring))
107 # simple-minded check that at least encoding a simple string works with the
107 # simple-minded check that at least encoding a simple string works with the
108 # encoding we got.
108 # encoding we got.
109 nt.assert_equal('test'.encode(encoding), 'test')
109 nt.assert_equal('test'.encode(encoding), 'test')
110
110
111
111
112 class NoInputEncodingTestCase(unittest.TestCase):
112 class NoInputEncodingTestCase(unittest.TestCase):
113 def setUp(self):
113 def setUp(self):
114 self.old_stdin = sys.stdin
114 self.old_stdin = sys.stdin
115 class X: pass
115 class X: pass
116 fake_stdin = X()
116 fake_stdin = X()
117 sys.stdin = fake_stdin
117 sys.stdin = fake_stdin
118
118
119 def test(self):
119 def test(self):
120 # Verify that if sys.stdin has no 'encoding' attribute we do the right
120 # Verify that if sys.stdin has no 'encoding' attribute we do the right
121 # thing
121 # thing
122 enc = isp.get_input_encoding()
122 enc = isp.get_input_encoding()
123 self.assertEqual(enc, 'ascii')
123 self.assertEqual(enc, 'ascii')
124
124
125 def tearDown(self):
125 def tearDown(self):
126 sys.stdin = self.old_stdin
126 sys.stdin = self.old_stdin
127
127
128
128
129 class InputSplitterTestCase(unittest.TestCase):
129 class InputSplitterTestCase(unittest.TestCase):
130 def setUp(self):
130 def setUp(self):
131 self.isp = isp.InputSplitter()
131 self.isp = isp.InputSplitter()
132
132
133 def test_reset(self):
133 def test_reset(self):
134 isp = self.isp
134 isp = self.isp
135 isp.push('x=1')
135 isp.push('x=1')
136 isp.reset()
136 isp.reset()
137 self.assertEqual(isp._buffer, [])
137 self.assertEqual(isp._buffer, [])
138 self.assertEqual(isp.indent_spaces, 0)
138 self.assertEqual(isp.indent_spaces, 0)
139 self.assertEqual(isp.source, '')
139 self.assertEqual(isp.source, '')
140 self.assertEqual(isp.code, None)
140 self.assertEqual(isp.code, None)
141 self.assertEqual(isp._is_complete, False)
141 self.assertEqual(isp._is_complete, False)
142
142
143 def test_source(self):
143 def test_source(self):
144 self.isp._store('1')
144 self.isp._store('1')
145 self.isp._store('2')
145 self.isp._store('2')
146 self.assertEqual(self.isp.source, '1\n2\n')
146 self.assertEqual(self.isp.source, '1\n2\n')
147 self.assertTrue(len(self.isp._buffer)>0)
147 self.assertTrue(len(self.isp._buffer)>0)
148 self.assertEqual(self.isp.source_reset(), '1\n2\n')
148 self.assertEqual(self.isp.source_reset(), '1\n2\n')
149 self.assertEqual(self.isp._buffer, [])
149 self.assertEqual(self.isp._buffer, [])
150 self.assertEqual(self.isp.source, '')
150 self.assertEqual(self.isp.source, '')
151
151
152 def test_indent(self):
152 def test_indent(self):
153 isp = self.isp # shorthand
153 isp = self.isp # shorthand
154 isp.push('x=1')
154 isp.push('x=1')
155 self.assertEqual(isp.indent_spaces, 0)
155 self.assertEqual(isp.indent_spaces, 0)
156 isp.push('if 1:\n x=1')
156 isp.push('if 1:\n x=1')
157 self.assertEqual(isp.indent_spaces, 4)
157 self.assertEqual(isp.indent_spaces, 4)
158 isp.push('y=2\n')
158 isp.push('y=2\n')
159 self.assertEqual(isp.indent_spaces, 0)
159 self.assertEqual(isp.indent_spaces, 0)
160 isp.push('if 1:')
160 isp.push('if 1:')
161 self.assertEqual(isp.indent_spaces, 4)
161 self.assertEqual(isp.indent_spaces, 4)
162 isp.push(' x=1')
162 isp.push(' x=1')
163 self.assertEqual(isp.indent_spaces, 4)
163 self.assertEqual(isp.indent_spaces, 4)
164 # Blank lines shouldn't change the indent level
164 # Blank lines shouldn't change the indent level
165 isp.push(' '*2)
165 isp.push(' '*2)
166 self.assertEqual(isp.indent_spaces, 4)
166 self.assertEqual(isp.indent_spaces, 4)
167
167
168 def test_indent2(self):
168 def test_indent2(self):
169 isp = self.isp
169 isp = self.isp
170 # When a multiline statement contains parens or multiline strings, we
170 # When a multiline statement contains parens or multiline strings, we
171 # shouldn't get confused.
171 # shouldn't get confused.
172 isp.push("if 1:")
172 isp.push("if 1:")
173 isp.push(" x = (1+\n 2)")
173 isp.push(" x = (1+\n 2)")
174 self.assertEqual(isp.indent_spaces, 4)
174 self.assertEqual(isp.indent_spaces, 4)
175
175
176 def test_dedent(self):
176 def test_dedent(self):
177 isp = self.isp # shorthand
177 isp = self.isp # shorthand
178 isp.push('if 1:')
178 isp.push('if 1:')
179 self.assertEqual(isp.indent_spaces, 4)
179 self.assertEqual(isp.indent_spaces, 4)
180 isp.push(' pass')
180 isp.push(' pass')
181 self.assertEqual(isp.indent_spaces, 0)
181 self.assertEqual(isp.indent_spaces, 0)
182
182
183 def test_push(self):
183 def test_push(self):
184 isp = self.isp
184 isp = self.isp
185 self.assertTrue(isp.push('x=1'))
185 self.assertTrue(isp.push('x=1'))
186
186
187 def test_push2(self):
187 def test_push2(self):
188 isp = self.isp
188 isp = self.isp
189 self.assertFalse(isp.push('if 1:'))
189 self.assertFalse(isp.push('if 1:'))
190 for line in [' x=1', '# a comment', ' y=2']:
190 for line in [' x=1', '# a comment', ' y=2']:
191 self.assertTrue(isp.push(line))
191 self.assertTrue(isp.push(line))
192
192
193 def test_push3(self):
193 def test_push3(self):
194 """Test input with leading whitespace"""
194 """Test input with leading whitespace"""
195 isp = self.isp
195 isp = self.isp
196 isp.push(' x=1')
196 isp.push(' x=1')
197 isp.push(' y=2')
197 isp.push(' y=2')
198 self.assertEqual(isp.source, 'if 1:\n x=1\n y=2\n')
198 self.assertEqual(isp.source, 'if 1:\n x=1\n y=2\n')
199
199
200 def test_replace_mode(self):
200 def test_replace_mode(self):
201 isp = self.isp
201 isp = self.isp
202 isp.input_mode = 'replace'
202 isp.input_mode = 'replace'
203 isp.push('x=1')
203 isp.push('x=1')
204 self.assertEqual(isp.source, 'x=1\n')
204 self.assertEqual(isp.source, 'x=1\n')
205 isp.push('x=2')
205 isp.push('x=2')
206 self.assertEqual(isp.source, 'x=2\n')
206 self.assertEqual(isp.source, 'x=2\n')
207
207
208 def test_push_accepts_more(self):
208 def test_push_accepts_more(self):
209 isp = self.isp
209 isp = self.isp
210 isp.push('x=1')
210 isp.push('x=1')
211 self.assertFalse(isp.push_accepts_more())
211 self.assertFalse(isp.push_accepts_more())
212
212
213 def test_push_accepts_more2(self):
213 def test_push_accepts_more2(self):
214 isp = self.isp
214 isp = self.isp
215 isp.push('if 1:')
215 isp.push('if 1:')
216 self.assertTrue(isp.push_accepts_more())
216 self.assertTrue(isp.push_accepts_more())
217 isp.push(' x=1')
217 isp.push(' x=1')
218 self.assertTrue(isp.push_accepts_more())
218 self.assertTrue(isp.push_accepts_more())
219 isp.push('')
219 isp.push('')
220 self.assertFalse(isp.push_accepts_more())
220 self.assertFalse(isp.push_accepts_more())
221
221
222 def test_push_accepts_more3(self):
222 def test_push_accepts_more3(self):
223 isp = self.isp
223 isp = self.isp
224 isp.push("x = (2+\n3)")
224 isp.push("x = (2+\n3)")
225 self.assertFalse(isp.push_accepts_more())
225 self.assertFalse(isp.push_accepts_more())
226
226
227 def test_push_accepts_more4(self):
227 def test_push_accepts_more4(self):
228 isp = self.isp
228 isp = self.isp
229 # When a multiline statement contains parens or multiline strings, we
229 # When a multiline statement contains parens or multiline strings, we
230 # shouldn't get confused.
230 # shouldn't get confused.
231 # FIXME: we should be able to better handle de-dents in statements like
231 # FIXME: we should be able to better handle de-dents in statements like
232 # multiline strings and multiline expressions (continued with \ or
232 # multiline strings and multiline expressions (continued with \ or
233 # parens). Right now we aren't handling the indentation tracking quite
233 # parens). Right now we aren't handling the indentation tracking quite
234 # correctly with this, though in practice it may not be too much of a
234 # correctly with this, though in practice it may not be too much of a
235 # problem. We'll need to see.
235 # problem. We'll need to see.
236 isp.push("if 1:")
236 isp.push("if 1:")
237 isp.push(" x = (2+")
237 isp.push(" x = (2+")
238 isp.push(" 3)")
238 isp.push(" 3)")
239 self.assertTrue(isp.push_accepts_more())
239 self.assertTrue(isp.push_accepts_more())
240 isp.push(" y = 3")
240 isp.push(" y = 3")
241 self.assertTrue(isp.push_accepts_more())
241 self.assertTrue(isp.push_accepts_more())
242 isp.push('')
242 isp.push('')
243 self.assertFalse(isp.push_accepts_more())
243 self.assertFalse(isp.push_accepts_more())
244
244
245 def test_syntax_error(self):
245 def test_syntax_error(self):
246 isp = self.isp
246 isp = self.isp
247 # Syntax errors immediately produce a 'ready' block, so the invalid
247 # Syntax errors immediately produce a 'ready' block, so the invalid
248 # Python can be sent to the kernel for evaluation with possible ipython
248 # Python can be sent to the kernel for evaluation with possible ipython
249 # special-syntax conversion.
249 # special-syntax conversion.
250 isp.push('run foo')
250 isp.push('run foo')
251 self.assertFalse(isp.push_accepts_more())
251 self.assertFalse(isp.push_accepts_more())
252
252
253 def check_split(self, block_lines, compile=True):
253 def check_split(self, block_lines, compile=True):
254 blocks = assemble(block_lines)
254 blocks = assemble(block_lines)
255 lines = ''.join(blocks)
255 lines = ''.join(blocks)
256 oblock = self.isp.split_blocks(lines)
256 oblock = self.isp.split_blocks(lines)
257 self.assertEqual(oblock, blocks)
257 self.assertEqual(oblock, blocks)
258 if compile:
258 if compile:
259 for block in blocks:
259 for block in blocks:
260 self.isp._compile(block)
260 self.isp._compile(block)
261
261
262 def test_split(self):
262 def test_split(self):
263 # All blocks of input we want to test in a list. The format for each
263 # All blocks of input we want to test in a list. The format for each
264 # block is a list of lists, with each inner lists consisting of all the
264 # block is a list of lists, with each inner lists consisting of all the
265 # lines (as single-lines) that should make up a sub-block.
265 # lines (as single-lines) that should make up a sub-block.
266
266
267 # Note: do NOT put here sub-blocks that don't compile, as the
267 # Note: do NOT put here sub-blocks that don't compile, as the
268 # check_split() routine makes a final verification pass to check that
268 # check_split() routine makes a final verification pass to check that
269 # each sub_block, as returned by split_blocks(), does compile
269 # each sub_block, as returned by split_blocks(), does compile
270 # correctly.
270 # correctly.
271 all_blocks = [ [['x=1']],
271 all_blocks = [ [['x=1']],
272
272
273 [['x=1'],
273 [['x=1'],
274 ['y=2']],
274 ['y=2']],
275
275
276 [['x=1'],
276 [['x=1'],
277 ['# a comment'],
277 ['# a comment'],
278 ['y=11']],
278 ['y=11']],
279
279
280 [['if 1:',
280 [['if 1:',
281 ' x=1'],
281 ' x=1'],
282 ['y=3']],
282 ['y=3']],
283
283
284 [['def f(x):',
284 [['def f(x):',
285 ' return x'],
285 ' return x'],
286 ['x=1']],
286 ['x=1']],
287
287
288 [['def f(x):',
288 [['def f(x):',
289 ' x+=1',
289 ' x+=1',
290 ' ',
290 ' ',
291 ' return x'],
291 ' return x'],
292 ['x=1']],
292 ['x=1']],
293
293
294 [['def f(x):',
294 [['def f(x):',
295 ' if x>0:',
295 ' if x>0:',
296 ' y=1',
296 ' y=1',
297 ' # a comment',
297 ' # a comment',
298 ' else:',
298 ' else:',
299 ' y=4',
299 ' y=4',
300 ' ',
300 ' ',
301 ' return y'],
301 ' return y'],
302 ['x=1'],
302 ['x=1'],
303 ['if 1:',
303 ['if 1:',
304 ' y=11'] ],
304 ' y=11'] ],
305
305
306 [['for i in range(10):'
306 [['for i in range(10):'
307 ' x=i**2']],
307 ' x=i**2']],
308
308
309 [['for i in range(10):'
309 [['for i in range(10):'
310 ' x=i**2'],
310 ' x=i**2'],
311 ['z = 1']],
311 ['z = 1']],
312 ]
312 ]
313 for block_lines in all_blocks:
313 for block_lines in all_blocks:
314 self.check_split(block_lines)
314 self.check_split(block_lines)
315
315
316 def test_split_syntax_errors(self):
316 def test_split_syntax_errors(self):
317 # Block splitting with invalid syntax
317 # Block splitting with invalid syntax
318 all_blocks = [ [['a syntax error']],
318 all_blocks = [ [['a syntax error']],
319
319
320 [['x=1'],
320 [['x=1'],
321 ['a syntax error']],
321 ['a syntax error']],
322
322
323 [['for i in range(10):'
323 [['for i in range(10):'
324 ' an error']],
324 ' an error']],
325
325
326 ]
326 ]
327 for block_lines in all_blocks:
327 for block_lines in all_blocks:
328 self.check_split(block_lines, compile=False)
328 self.check_split(block_lines, compile=False)
329
329
330
330
331 class InteractiveLoopTestCase(unittest.TestCase):
331 class InteractiveLoopTestCase(unittest.TestCase):
332 """Tests for an interactive loop like a python shell.
332 """Tests for an interactive loop like a python shell.
333 """
333 """
334 def check_ns(self, lines, ns):
334 def check_ns(self, lines, ns):
335 """Validate that the given input lines produce the resulting namespace.
335 """Validate that the given input lines produce the resulting namespace.
336
336
337 Note: the input lines are given exactly as they would be typed in an
337 Note: the input lines are given exactly as they would be typed in an
338 auto-indenting environment, as mini_interactive_loop above already does
338 auto-indenting environment, as mini_interactive_loop above already does
339 auto-indenting and prepends spaces to the input.
339 auto-indenting and prepends spaces to the input.
340 """
340 """
341 src = mini_interactive_loop(pseudo_input(lines))
341 src = mini_interactive_loop(pseudo_input(lines))
342 test_ns = {}
342 test_ns = {}
343 exec src in test_ns
343 exec src in test_ns
344 # We can't check that the provided ns is identical to the test_ns,
344 # We can't check that the provided ns is identical to the test_ns,
345 # because Python fills test_ns with extra keys (copyright, etc). But
345 # because Python fills test_ns with extra keys (copyright, etc). But
346 # we can check that the given dict is *contained* in test_ns
346 # we can check that the given dict is *contained* in test_ns
347 for k,v in ns.items():
347 for k,v in ns.items():
348 self.assertEqual(test_ns[k], v)
348 self.assertEqual(test_ns[k], v)
349
349
350 def test_simple(self):
350 def test_simple(self):
351 self.check_ns(['x=1'], dict(x=1))
351 self.check_ns(['x=1'], dict(x=1))
352
352
353 def test_simple2(self):
353 def test_simple2(self):
354 self.check_ns(['if 1:', 'x=2'], dict(x=2))
354 self.check_ns(['if 1:', 'x=2'], dict(x=2))
355
355
356 def test_xy(self):
356 def test_xy(self):
357 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
357 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
358
358
359 def test_abc(self):
359 def test_abc(self):
360 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
360 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
361
361
362 def test_multi(self):
362 def test_multi(self):
363 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
363 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
364
364
365
366 class IPythonInputTestCase(InputSplitterTestCase):
367 def setUp(self):
368 self.isp = isp.IPythonInputSplitter()
369
370
371 # Transformer tests
372 def transform_checker(tests, func):
373 """Utility to loop over test inputs"""
374 for inp, tr in tests:
375 nt.assert_equals(func(inp), tr)
376
377
378 def test_assign_system():
379 tests = [('a =! ls', 'a = get_ipython().magic("sc -l = ls")'),
380 ('b = !ls', 'b = get_ipython().magic("sc -l = ls")'),
381 ('x=1','x=1')]
382 transform_checker(tests, isp.transform_assign_system)
383
384
385 def test_assign_magic():
386 tests = [('a =% who', 'a = get_ipython().magic("who")'),
387 ('b = %who', 'b = get_ipython().magic("who")'),
388 ('x=1','x=1')]
389 transform_checker(tests, isp.transform_assign_magic)
390
391
392 def test_classic_prompt():
393 tests = [('>>> x=1', 'x=1'),
394 ('>>> for i in range(10):','for i in range(10):'),
395 ('... print i',' print i'),
396 ('...', ''),
397 ('x=1','x=1')
398 ]
399 transform_checker(tests, isp.transform_classic_prompt)
400
401
402 def test_ipy_prompt():
403 tests = [('In [1]: x=1', 'x=1'),
404 ('In [24]: for i in range(10):','for i in range(10):'),
405 (' ....: print i',' print i'),
406 (' ....: ', ''),
407 ('x=1', 'x=1'), # normal input is unmodified
408 (' ','') # blank lines are just collapsed
409 ]
410 transform_checker(tests, isp.transform_ipy_prompt)
411
General Comments 0
You need to be logged in to leave comments. Login now