##// END OF EJS Templates
SyntaxWarning in compile indicates invalid input
Min RK -
Show More
@@ -1,679 +1,682 b''
1 """Input handling and transformation machinery.
1 """Input handling and transformation machinery.
2
2
3 The first class in this module, :class:`InputSplitter`, is designed to tell when
3 The first class in this module, :class:`InputSplitter`, is designed to tell when
4 input from a line-oriented frontend is complete and should be executed, and when
4 input from a line-oriented frontend is complete and should be executed, and when
5 the user should be prompted for another line of code instead. The name 'input
5 the user should be prompted for another line of code instead. The name 'input
6 splitter' is largely for historical reasons.
6 splitter' is largely for historical reasons.
7
7
8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
9 with full support for the extended IPython syntax (magics, system calls, etc).
9 with full support for the extended IPython syntax (magics, system calls, etc).
10 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
10 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
11 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
11 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
12 and stores the results.
12 and stores the results.
13
13
14 For more details, see the class docstrings below.
14 For more details, see the class docstrings below.
15 """
15 """
16
16
17 # Copyright (c) IPython Development Team.
17 # Copyright (c) IPython Development Team.
18 # Distributed under the terms of the Modified BSD License.
18 # Distributed under the terms of the Modified BSD License.
19 import ast
19 import ast
20 import codeop
20 import codeop
21 import re
21 import re
22 import sys
22 import sys
23 import warnings
23
24
24 from IPython.utils.py3compat import cast_unicode
25 from IPython.utils.py3compat import cast_unicode
25 from IPython.core.inputtransformer import (leading_indent,
26 from IPython.core.inputtransformer import (leading_indent,
26 classic_prompt,
27 classic_prompt,
27 ipy_prompt,
28 ipy_prompt,
28 strip_encoding_cookie,
29 strip_encoding_cookie,
29 cellmagic,
30 cellmagic,
30 assemble_logical_lines,
31 assemble_logical_lines,
31 help_end,
32 help_end,
32 escaped_commands,
33 escaped_commands,
33 assign_from_magic,
34 assign_from_magic,
34 assign_from_system,
35 assign_from_system,
35 assemble_python_lines,
36 assemble_python_lines,
36 )
37 )
37
38
38 # These are available in this module for backwards compatibility.
39 # These are available in this module for backwards compatibility.
39 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
40 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
40 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
41 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
41 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
42 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
42
43
43 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
44 # Utilities
45 # Utilities
45 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
46
47
47 # FIXME: These are general-purpose utilities that later can be moved to the
48 # FIXME: These are general-purpose utilities that later can be moved to the
48 # general ward. Kept here for now because we're being very strict about test
49 # general ward. Kept here for now because we're being very strict about test
49 # coverage with this code, and this lets us ensure that we keep 100% coverage
50 # coverage with this code, and this lets us ensure that we keep 100% coverage
50 # while developing.
51 # while developing.
51
52
52 # compiled regexps for autoindent management
53 # compiled regexps for autoindent management
53 dedent_re = re.compile('|'.join([
54 dedent_re = re.compile('|'.join([
54 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
55 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
55 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
56 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
56 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
57 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
57 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
58 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
58 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
59 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
59 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
60 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
60 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
61 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
61 ]))
62 ]))
62 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
63 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
63
64
64 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
65 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
65 # before pure comments
66 # before pure comments
66 comment_line_re = re.compile('^\s*\#')
67 comment_line_re = re.compile('^\s*\#')
67
68
68
69
69 def num_ini_spaces(s):
70 def num_ini_spaces(s):
70 """Return the number of initial spaces in a string.
71 """Return the number of initial spaces in a string.
71
72
72 Note that tabs are counted as a single space. For now, we do *not* support
73 Note that tabs are counted as a single space. For now, we do *not* support
73 mixing of tabs and spaces in the user's input.
74 mixing of tabs and spaces in the user's input.
74
75
75 Parameters
76 Parameters
76 ----------
77 ----------
77 s : string
78 s : string
78
79
79 Returns
80 Returns
80 -------
81 -------
81 n : int
82 n : int
82 """
83 """
83
84
84 ini_spaces = ini_spaces_re.match(s)
85 ini_spaces = ini_spaces_re.match(s)
85 if ini_spaces:
86 if ini_spaces:
86 return ini_spaces.end()
87 return ini_spaces.end()
87 else:
88 else:
88 return 0
89 return 0
89
90
90 def last_blank(src):
91 def last_blank(src):
91 """Determine if the input source ends in a blank.
92 """Determine if the input source ends in a blank.
92
93
93 A blank is either a newline or a line consisting of whitespace.
94 A blank is either a newline or a line consisting of whitespace.
94
95
95 Parameters
96 Parameters
96 ----------
97 ----------
97 src : string
98 src : string
98 A single or multiline string.
99 A single or multiline string.
99 """
100 """
100 if not src: return False
101 if not src: return False
101 ll = src.splitlines()[-1]
102 ll = src.splitlines()[-1]
102 return (ll == '') or ll.isspace()
103 return (ll == '') or ll.isspace()
103
104
104
105
105 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
106 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
106 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
107 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
107
108
108 def last_two_blanks(src):
109 def last_two_blanks(src):
109 """Determine if the input source ends in two blanks.
110 """Determine if the input source ends in two blanks.
110
111
111 A blank is either a newline or a line consisting of whitespace.
112 A blank is either a newline or a line consisting of whitespace.
112
113
113 Parameters
114 Parameters
114 ----------
115 ----------
115 src : string
116 src : string
116 A single or multiline string.
117 A single or multiline string.
117 """
118 """
118 if not src: return False
119 if not src: return False
119 # The logic here is tricky: I couldn't get a regexp to work and pass all
120 # The logic here is tricky: I couldn't get a regexp to work and pass all
120 # the tests, so I took a different approach: split the source by lines,
121 # the tests, so I took a different approach: split the source by lines,
121 # grab the last two and prepend '###\n' as a stand-in for whatever was in
122 # grab the last two and prepend '###\n' as a stand-in for whatever was in
122 # the body before the last two lines. Then, with that structure, it's
123 # the body before the last two lines. Then, with that structure, it's
123 # possible to analyze with two regexps. Not the most elegant solution, but
124 # possible to analyze with two regexps. Not the most elegant solution, but
124 # it works. If anyone tries to change this logic, make sure to validate
125 # it works. If anyone tries to change this logic, make sure to validate
125 # the whole test suite first!
126 # the whole test suite first!
126 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
127 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
127 return (bool(last_two_blanks_re.match(new_src)) or
128 return (bool(last_two_blanks_re.match(new_src)) or
128 bool(last_two_blanks_re2.match(new_src)) )
129 bool(last_two_blanks_re2.match(new_src)) )
129
130
130
131
131 def remove_comments(src):
132 def remove_comments(src):
132 """Remove all comments from input source.
133 """Remove all comments from input source.
133
134
134 Note: comments are NOT recognized inside of strings!
135 Note: comments are NOT recognized inside of strings!
135
136
136 Parameters
137 Parameters
137 ----------
138 ----------
138 src : string
139 src : string
139 A single or multiline input string.
140 A single or multiline input string.
140
141
141 Returns
142 Returns
142 -------
143 -------
143 String with all Python comments removed.
144 String with all Python comments removed.
144 """
145 """
145
146
146 return re.sub('#.*', '', src)
147 return re.sub('#.*', '', src)
147
148
148
149
149 def get_input_encoding():
150 def get_input_encoding():
150 """Return the default standard input encoding.
151 """Return the default standard input encoding.
151
152
152 If sys.stdin has no encoding, 'ascii' is returned."""
153 If sys.stdin has no encoding, 'ascii' is returned."""
153 # There are strange environments for which sys.stdin.encoding is None. We
154 # There are strange environments for which sys.stdin.encoding is None. We
154 # ensure that a valid encoding is returned.
155 # ensure that a valid encoding is returned.
155 encoding = getattr(sys.stdin, 'encoding', None)
156 encoding = getattr(sys.stdin, 'encoding', None)
156 if encoding is None:
157 if encoding is None:
157 encoding = 'ascii'
158 encoding = 'ascii'
158 return encoding
159 return encoding
159
160
160 #-----------------------------------------------------------------------------
161 #-----------------------------------------------------------------------------
161 # Classes and functions for normal Python syntax handling
162 # Classes and functions for normal Python syntax handling
162 #-----------------------------------------------------------------------------
163 #-----------------------------------------------------------------------------
163
164
164 class InputSplitter(object):
165 class InputSplitter(object):
165 r"""An object that can accumulate lines of Python source before execution.
166 r"""An object that can accumulate lines of Python source before execution.
166
167
167 This object is designed to be fed python source line-by-line, using
168 This object is designed to be fed python source line-by-line, using
168 :meth:`push`. It will return on each push whether the currently pushed
169 :meth:`push`. It will return on each push whether the currently pushed
169 code could be executed already. In addition, it provides a method called
170 code could be executed already. In addition, it provides a method called
170 :meth:`push_accepts_more` that can be used to query whether more input
171 :meth:`push_accepts_more` that can be used to query whether more input
171 can be pushed into a single interactive block.
172 can be pushed into a single interactive block.
172
173
173 This is a simple example of how an interactive terminal-based client can use
174 This is a simple example of how an interactive terminal-based client can use
174 this tool::
175 this tool::
175
176
176 isp = InputSplitter()
177 isp = InputSplitter()
177 while isp.push_accepts_more():
178 while isp.push_accepts_more():
178 indent = ' '*isp.indent_spaces
179 indent = ' '*isp.indent_spaces
179 prompt = '>>> ' + indent
180 prompt = '>>> ' + indent
180 line = indent + raw_input(prompt)
181 line = indent + raw_input(prompt)
181 isp.push(line)
182 isp.push(line)
182 print 'Input source was:\n', isp.source_reset(),
183 print 'Input source was:\n', isp.source_reset(),
183 """
184 """
184 # Number of spaces of indentation computed from input that has been pushed
185 # Number of spaces of indentation computed from input that has been pushed
185 # so far. This is the attributes callers should query to get the current
186 # so far. This is the attributes callers should query to get the current
186 # indentation level, in order to provide auto-indent facilities.
187 # indentation level, in order to provide auto-indent facilities.
187 indent_spaces = 0
188 indent_spaces = 0
188 # String, indicating the default input encoding. It is computed by default
189 # String, indicating the default input encoding. It is computed by default
189 # at initialization time via get_input_encoding(), but it can be reset by a
190 # at initialization time via get_input_encoding(), but it can be reset by a
190 # client with specific knowledge of the encoding.
191 # client with specific knowledge of the encoding.
191 encoding = ''
192 encoding = ''
192 # String where the current full source input is stored, properly encoded.
193 # String where the current full source input is stored, properly encoded.
193 # Reading this attribute is the normal way of querying the currently pushed
194 # Reading this attribute is the normal way of querying the currently pushed
194 # source code, that has been properly encoded.
195 # source code, that has been properly encoded.
195 source = ''
196 source = ''
196 # Code object corresponding to the current source. It is automatically
197 # Code object corresponding to the current source. It is automatically
197 # synced to the source, so it can be queried at any time to obtain the code
198 # synced to the source, so it can be queried at any time to obtain the code
198 # object; it will be None if the source doesn't compile to valid Python.
199 # object; it will be None if the source doesn't compile to valid Python.
199 code = None
200 code = None
200
201
201 # Private attributes
202 # Private attributes
202
203
203 # List with lines of input accumulated so far
204 # List with lines of input accumulated so far
204 _buffer = None
205 _buffer = None
205 # Command compiler
206 # Command compiler
206 _compile = None
207 _compile = None
207 # Mark when input has changed indentation all the way back to flush-left
208 # Mark when input has changed indentation all the way back to flush-left
208 _full_dedent = False
209 _full_dedent = False
209 # Boolean indicating whether the current block is complete
210 # Boolean indicating whether the current block is complete
210 _is_complete = None
211 _is_complete = None
211 # Boolean indicating whether the current block has an unrecoverable syntax error
212 # Boolean indicating whether the current block has an unrecoverable syntax error
212 _is_invalid = False
213 _is_invalid = False
213
214
214 def __init__(self):
215 def __init__(self):
215 """Create a new InputSplitter instance.
216 """Create a new InputSplitter instance.
216 """
217 """
217 self._buffer = []
218 self._buffer = []
218 self._compile = codeop.CommandCompiler()
219 self._compile = codeop.CommandCompiler()
219 self.encoding = get_input_encoding()
220 self.encoding = get_input_encoding()
220
221
221 def reset(self):
222 def reset(self):
222 """Reset the input buffer and associated state."""
223 """Reset the input buffer and associated state."""
223 self.indent_spaces = 0
224 self.indent_spaces = 0
224 self._buffer[:] = []
225 self._buffer[:] = []
225 self.source = ''
226 self.source = ''
226 self.code = None
227 self.code = None
227 self._is_complete = False
228 self._is_complete = False
228 self._is_invalid = False
229 self._is_invalid = False
229 self._full_dedent = False
230 self._full_dedent = False
230
231
231 def source_reset(self):
232 def source_reset(self):
232 """Return the input source and perform a full reset.
233 """Return the input source and perform a full reset.
233 """
234 """
234 out = self.source
235 out = self.source
235 self.reset()
236 self.reset()
236 return out
237 return out
237
238
238 def check_complete(self, source):
239 def check_complete(self, source):
239 """Return whether a block of code is ready to execute, or should be continued
240 """Return whether a block of code is ready to execute, or should be continued
240
241
241 This is a non-stateful API, and will reset the state of this InputSplitter.
242 This is a non-stateful API, and will reset the state of this InputSplitter.
242
243
243 Parameters
244 Parameters
244 ----------
245 ----------
245 source : string
246 source : string
246 Python input code, which can be multiline.
247 Python input code, which can be multiline.
247
248
248 Returns
249 Returns
249 -------
250 -------
250 status : str
251 status : str
251 One of 'complete', 'incomplete', or 'invalid' if source is not a
252 One of 'complete', 'incomplete', or 'invalid' if source is not a
252 prefix of valid code.
253 prefix of valid code.
253 indent_spaces : int or None
254 indent_spaces : int or None
254 The number of spaces by which to indent the next line of code. If
255 The number of spaces by which to indent the next line of code. If
255 status is not 'incomplete', this is None.
256 status is not 'incomplete', this is None.
256 """
257 """
257 self.reset()
258 self.reset()
258 try:
259 try:
259 self.push(source)
260 self.push(source)
260 except SyntaxError:
261 except SyntaxError:
261 # Transformers in IPythonInputSplitter can raise SyntaxError,
262 # Transformers in IPythonInputSplitter can raise SyntaxError,
262 # which push() will not catch.
263 # which push() will not catch.
263 return 'invalid', None
264 return 'invalid', None
264 else:
265 else:
265 if self._is_invalid:
266 if self._is_invalid:
266 return 'invalid', None
267 return 'invalid', None
267 elif self.push_accepts_more():
268 elif self.push_accepts_more():
268 return 'incomplete', self.indent_spaces
269 return 'incomplete', self.indent_spaces
269 else:
270 else:
270 return 'complete', None
271 return 'complete', None
271 finally:
272 finally:
272 self.reset()
273 self.reset()
273
274
274 def push(self, lines):
275 def push(self, lines):
275 """Push one or more lines of input.
276 """Push one or more lines of input.
276
277
277 This stores the given lines and returns a status code indicating
278 This stores the given lines and returns a status code indicating
278 whether the code forms a complete Python block or not.
279 whether the code forms a complete Python block or not.
279
280
280 Any exceptions generated in compilation are swallowed, but if an
281 Any exceptions generated in compilation are swallowed, but if an
281 exception was produced, the method returns True.
282 exception was produced, the method returns True.
282
283
283 Parameters
284 Parameters
284 ----------
285 ----------
285 lines : string
286 lines : string
286 One or more lines of Python input.
287 One or more lines of Python input.
287
288
288 Returns
289 Returns
289 -------
290 -------
290 is_complete : boolean
291 is_complete : boolean
291 True if the current input source (the result of the current input
292 True if the current input source (the result of the current input
292 plus prior inputs) forms a complete Python execution block. Note that
293 plus prior inputs) forms a complete Python execution block. Note that
293 this value is also stored as a private attribute (``_is_complete``), so it
294 this value is also stored as a private attribute (``_is_complete``), so it
294 can be queried at any time.
295 can be queried at any time.
295 """
296 """
296 self._store(lines)
297 self._store(lines)
297 source = self.source
298 source = self.source
298
299
299 # Before calling _compile(), reset the code object to None so that if an
300 # Before calling _compile(), reset the code object to None so that if an
300 # exception is raised in compilation, we don't mislead by having
301 # exception is raised in compilation, we don't mislead by having
301 # inconsistent code/source attributes.
302 # inconsistent code/source attributes.
302 self.code, self._is_complete = None, None
303 self.code, self._is_complete = None, None
303 self._is_invalid = False
304 self._is_invalid = False
304
305
305 # Honor termination lines properly
306 # Honor termination lines properly
306 if source.endswith('\\\n'):
307 if source.endswith('\\\n'):
307 return False
308 return False
308
309
309 self._update_indent(lines)
310 self._update_indent(lines)
310 try:
311 try:
311 self.code = self._compile(source, symbol="exec")
312 with warnings.catch_warnings():
313 warnings.simplefilter('error', SyntaxWarning)
314 self.code = self._compile(source, symbol="exec")
312 # Invalid syntax can produce any of a number of different errors from
315 # Invalid syntax can produce any of a number of different errors from
313 # inside the compiler, so we have to catch them all. Syntax errors
316 # inside the compiler, so we have to catch them all. Syntax errors
314 # immediately produce a 'ready' block, so the invalid Python can be
317 # immediately produce a 'ready' block, so the invalid Python can be
315 # sent to the kernel for evaluation with possible ipython
318 # sent to the kernel for evaluation with possible ipython
316 # special-syntax conversion.
319 # special-syntax conversion.
317 except (SyntaxError, OverflowError, ValueError, TypeError,
320 except (SyntaxError, OverflowError, ValueError, TypeError,
318 MemoryError):
321 MemoryError, SyntaxWarning):
319 self._is_complete = True
322 self._is_complete = True
320 self._is_invalid = True
323 self._is_invalid = True
321 else:
324 else:
322 # Compilation didn't produce any exceptions (though it may not have
325 # Compilation didn't produce any exceptions (though it may not have
323 # given a complete code object)
326 # given a complete code object)
324 self._is_complete = self.code is not None
327 self._is_complete = self.code is not None
325
328
326 return self._is_complete
329 return self._is_complete
327
330
328 def push_accepts_more(self):
331 def push_accepts_more(self):
329 """Return whether a block of interactive input can accept more input.
332 """Return whether a block of interactive input can accept more input.
330
333
331 This method is meant to be used by line-oriented frontends, who need to
334 This method is meant to be used by line-oriented frontends, who need to
332 guess whether a block is complete or not based solely on prior and
335 guess whether a block is complete or not based solely on prior and
333 current input lines. The InputSplitter considers it has a complete
336 current input lines. The InputSplitter considers it has a complete
334 interactive block and will not accept more input when either:
337 interactive block and will not accept more input when either:
335
338
336 * A SyntaxError is raised
339 * A SyntaxError is raised
337
340
338 * The code is complete and consists of a single line or a single
341 * The code is complete and consists of a single line or a single
339 non-compound statement
342 non-compound statement
340
343
341 * The code is complete and has a blank line at the end
344 * The code is complete and has a blank line at the end
342
345
343 If the current input produces a syntax error, this method immediately
346 If the current input produces a syntax error, this method immediately
344 returns False but does *not* raise the syntax error exception, as
347 returns False but does *not* raise the syntax error exception, as
345 typically clients will want to send invalid syntax to an execution
348 typically clients will want to send invalid syntax to an execution
346 backend which might convert the invalid syntax into valid Python via
349 backend which might convert the invalid syntax into valid Python via
347 one of the dynamic IPython mechanisms.
350 one of the dynamic IPython mechanisms.
348 """
351 """
349
352
350 # With incomplete input, unconditionally accept more
353 # With incomplete input, unconditionally accept more
351 # A syntax error also sets _is_complete to True - see push()
354 # A syntax error also sets _is_complete to True - see push()
352 if not self._is_complete:
355 if not self._is_complete:
353 #print("Not complete") # debug
356 #print("Not complete") # debug
354 return True
357 return True
355
358
356 # The user can make any (complete) input execute by leaving a blank line
359 # The user can make any (complete) input execute by leaving a blank line
357 last_line = self.source.splitlines()[-1]
360 last_line = self.source.splitlines()[-1]
358 if (not last_line) or last_line.isspace():
361 if (not last_line) or last_line.isspace():
359 #print("Blank line") # debug
362 #print("Blank line") # debug
360 return False
363 return False
361
364
362 # If there's just a single line or AST node, and we're flush left, as is
365 # If there's just a single line or AST node, and we're flush left, as is
363 # the case after a simple statement such as 'a=1', we want to execute it
366 # the case after a simple statement such as 'a=1', we want to execute it
364 # straight away.
367 # straight away.
365 if self.indent_spaces==0:
368 if self.indent_spaces==0:
366 if len(self.source.splitlines()) <= 1:
369 if len(self.source.splitlines()) <= 1:
367 return False
370 return False
368
371
369 try:
372 try:
370 code_ast = ast.parse(u''.join(self._buffer))
373 code_ast = ast.parse(u''.join(self._buffer))
371 except Exception:
374 except Exception:
372 #print("Can't parse AST") # debug
375 #print("Can't parse AST") # debug
373 return False
376 return False
374 else:
377 else:
375 if len(code_ast.body) == 1 and \
378 if len(code_ast.body) == 1 and \
376 not hasattr(code_ast.body[0], 'body'):
379 not hasattr(code_ast.body[0], 'body'):
377 #print("Simple statement") # debug
380 #print("Simple statement") # debug
378 return False
381 return False
379
382
380 # General fallback - accept more code
383 # General fallback - accept more code
381 return True
384 return True
382
385
383 #------------------------------------------------------------------------
386 #------------------------------------------------------------------------
384 # Private interface
387 # Private interface
385 #------------------------------------------------------------------------
388 #------------------------------------------------------------------------
386
389
387 def _find_indent(self, line):
390 def _find_indent(self, line):
388 """Compute the new indentation level for a single line.
391 """Compute the new indentation level for a single line.
389
392
390 Parameters
393 Parameters
391 ----------
394 ----------
392 line : str
395 line : str
393 A single new line of non-whitespace, non-comment Python input.
396 A single new line of non-whitespace, non-comment Python input.
394
397
395 Returns
398 Returns
396 -------
399 -------
397 indent_spaces : int
400 indent_spaces : int
398 New value for the indent level (it may be equal to self.indent_spaces
401 New value for the indent level (it may be equal to self.indent_spaces
399 if indentation doesn't change.
402 if indentation doesn't change.
400
403
401 full_dedent : boolean
404 full_dedent : boolean
402 Whether the new line causes a full flush-left dedent.
405 Whether the new line causes a full flush-left dedent.
403 """
406 """
404 indent_spaces = self.indent_spaces
407 indent_spaces = self.indent_spaces
405 full_dedent = self._full_dedent
408 full_dedent = self._full_dedent
406
409
407 inisp = num_ini_spaces(line)
410 inisp = num_ini_spaces(line)
408 if inisp < indent_spaces:
411 if inisp < indent_spaces:
409 indent_spaces = inisp
412 indent_spaces = inisp
410 if indent_spaces <= 0:
413 if indent_spaces <= 0:
411 #print 'Full dedent in text',self.source # dbg
414 #print 'Full dedent in text',self.source # dbg
412 full_dedent = True
415 full_dedent = True
413
416
414 if line.rstrip()[-1] == ':':
417 if line.rstrip()[-1] == ':':
415 indent_spaces += 4
418 indent_spaces += 4
416 elif dedent_re.match(line):
419 elif dedent_re.match(line):
417 indent_spaces -= 4
420 indent_spaces -= 4
418 if indent_spaces <= 0:
421 if indent_spaces <= 0:
419 full_dedent = True
422 full_dedent = True
420
423
421 # Safety
424 # Safety
422 if indent_spaces < 0:
425 if indent_spaces < 0:
423 indent_spaces = 0
426 indent_spaces = 0
424 #print 'safety' # dbg
427 #print 'safety' # dbg
425
428
426 return indent_spaces, full_dedent
429 return indent_spaces, full_dedent
427
430
428 def _update_indent(self, lines):
431 def _update_indent(self, lines):
429 for line in remove_comments(lines).splitlines():
432 for line in remove_comments(lines).splitlines():
430 if line and not line.isspace():
433 if line and not line.isspace():
431 self.indent_spaces, self._full_dedent = self._find_indent(line)
434 self.indent_spaces, self._full_dedent = self._find_indent(line)
432
435
433 def _store(self, lines, buffer=None, store='source'):
436 def _store(self, lines, buffer=None, store='source'):
434 """Store one or more lines of input.
437 """Store one or more lines of input.
435
438
436 If input lines are not newline-terminated, a newline is automatically
439 If input lines are not newline-terminated, a newline is automatically
437 appended."""
440 appended."""
438
441
439 if buffer is None:
442 if buffer is None:
440 buffer = self._buffer
443 buffer = self._buffer
441
444
442 if lines.endswith('\n'):
445 if lines.endswith('\n'):
443 buffer.append(lines)
446 buffer.append(lines)
444 else:
447 else:
445 buffer.append(lines+'\n')
448 buffer.append(lines+'\n')
446 setattr(self, store, self._set_source(buffer))
449 setattr(self, store, self._set_source(buffer))
447
450
448 def _set_source(self, buffer):
451 def _set_source(self, buffer):
449 return u''.join(buffer)
452 return u''.join(buffer)
450
453
451
454
452 class IPythonInputSplitter(InputSplitter):
455 class IPythonInputSplitter(InputSplitter):
453 """An input splitter that recognizes all of IPython's special syntax."""
456 """An input splitter that recognizes all of IPython's special syntax."""
454
457
455 # String with raw, untransformed input.
458 # String with raw, untransformed input.
456 source_raw = ''
459 source_raw = ''
457
460
458 # Flag to track when a transformer has stored input that it hasn't given
461 # Flag to track when a transformer has stored input that it hasn't given
459 # back yet.
462 # back yet.
460 transformer_accumulating = False
463 transformer_accumulating = False
461
464
462 # Flag to track when assemble_python_lines has stored input that it hasn't
465 # Flag to track when assemble_python_lines has stored input that it hasn't
463 # given back yet.
466 # given back yet.
464 within_python_line = False
467 within_python_line = False
465
468
466 # Private attributes
469 # Private attributes
467
470
468 # List with lines of raw input accumulated so far.
471 # List with lines of raw input accumulated so far.
469 _buffer_raw = None
472 _buffer_raw = None
470
473
471 def __init__(self, line_input_checker=True, physical_line_transforms=None,
474 def __init__(self, line_input_checker=True, physical_line_transforms=None,
472 logical_line_transforms=None, python_line_transforms=None):
475 logical_line_transforms=None, python_line_transforms=None):
473 super(IPythonInputSplitter, self).__init__()
476 super(IPythonInputSplitter, self).__init__()
474 self._buffer_raw = []
477 self._buffer_raw = []
475 self._validate = True
478 self._validate = True
476
479
477 if physical_line_transforms is not None:
480 if physical_line_transforms is not None:
478 self.physical_line_transforms = physical_line_transforms
481 self.physical_line_transforms = physical_line_transforms
479 else:
482 else:
480 self.physical_line_transforms = [
483 self.physical_line_transforms = [
481 leading_indent(),
484 leading_indent(),
482 classic_prompt(),
485 classic_prompt(),
483 ipy_prompt(),
486 ipy_prompt(),
484 strip_encoding_cookie(),
487 strip_encoding_cookie(),
485 cellmagic(end_on_blank_line=line_input_checker),
488 cellmagic(end_on_blank_line=line_input_checker),
486 ]
489 ]
487
490
488 self.assemble_logical_lines = assemble_logical_lines()
491 self.assemble_logical_lines = assemble_logical_lines()
489 if logical_line_transforms is not None:
492 if logical_line_transforms is not None:
490 self.logical_line_transforms = logical_line_transforms
493 self.logical_line_transforms = logical_line_transforms
491 else:
494 else:
492 self.logical_line_transforms = [
495 self.logical_line_transforms = [
493 help_end(),
496 help_end(),
494 escaped_commands(),
497 escaped_commands(),
495 assign_from_magic(),
498 assign_from_magic(),
496 assign_from_system(),
499 assign_from_system(),
497 ]
500 ]
498
501
499 self.assemble_python_lines = assemble_python_lines()
502 self.assemble_python_lines = assemble_python_lines()
500 if python_line_transforms is not None:
503 if python_line_transforms is not None:
501 self.python_line_transforms = python_line_transforms
504 self.python_line_transforms = python_line_transforms
502 else:
505 else:
503 # We don't use any of these at present
506 # We don't use any of these at present
504 self.python_line_transforms = []
507 self.python_line_transforms = []
505
508
506 @property
509 @property
507 def transforms(self):
510 def transforms(self):
508 "Quick access to all transformers."
511 "Quick access to all transformers."
509 return self.physical_line_transforms + \
512 return self.physical_line_transforms + \
510 [self.assemble_logical_lines] + self.logical_line_transforms + \
513 [self.assemble_logical_lines] + self.logical_line_transforms + \
511 [self.assemble_python_lines] + self.python_line_transforms
514 [self.assemble_python_lines] + self.python_line_transforms
512
515
513 @property
516 @property
514 def transforms_in_use(self):
517 def transforms_in_use(self):
515 """Transformers, excluding logical line transformers if we're in a
518 """Transformers, excluding logical line transformers if we're in a
516 Python line."""
519 Python line."""
517 t = self.physical_line_transforms[:]
520 t = self.physical_line_transforms[:]
518 if not self.within_python_line:
521 if not self.within_python_line:
519 t += [self.assemble_logical_lines] + self.logical_line_transforms
522 t += [self.assemble_logical_lines] + self.logical_line_transforms
520 return t + [self.assemble_python_lines] + self.python_line_transforms
523 return t + [self.assemble_python_lines] + self.python_line_transforms
521
524
522 def reset(self):
525 def reset(self):
523 """Reset the input buffer and associated state."""
526 """Reset the input buffer and associated state."""
524 super(IPythonInputSplitter, self).reset()
527 super(IPythonInputSplitter, self).reset()
525 self._buffer_raw[:] = []
528 self._buffer_raw[:] = []
526 self.source_raw = ''
529 self.source_raw = ''
527 self.transformer_accumulating = False
530 self.transformer_accumulating = False
528 self.within_python_line = False
531 self.within_python_line = False
529
532
530 for t in self.transforms:
533 for t in self.transforms:
531 try:
534 try:
532 t.reset()
535 t.reset()
533 except SyntaxError:
536 except SyntaxError:
534 # Nothing that calls reset() expects to handle transformer
537 # Nothing that calls reset() expects to handle transformer
535 # errors
538 # errors
536 pass
539 pass
537
540
538 def flush_transformers(self):
541 def flush_transformers(self):
539 def _flush(transform, outs):
542 def _flush(transform, outs):
540 """yield transformed lines
543 """yield transformed lines
541
544
542 always strings, never None
545 always strings, never None
543
546
544 transform: the current transform
547 transform: the current transform
545 outs: an iterable of previously transformed inputs.
548 outs: an iterable of previously transformed inputs.
546 Each may be multiline, which will be passed
549 Each may be multiline, which will be passed
547 one line at a time to transform.
550 one line at a time to transform.
548 """
551 """
549 for out in outs:
552 for out in outs:
550 for line in out.splitlines():
553 for line in out.splitlines():
551 # push one line at a time
554 # push one line at a time
552 tmp = transform.push(line)
555 tmp = transform.push(line)
553 if tmp is not None:
556 if tmp is not None:
554 yield tmp
557 yield tmp
555
558
556 # reset the transform
559 # reset the transform
557 tmp = transform.reset()
560 tmp = transform.reset()
558 if tmp is not None:
561 if tmp is not None:
559 yield tmp
562 yield tmp
560
563
561 out = []
564 out = []
562 for t in self.transforms_in_use:
565 for t in self.transforms_in_use:
563 out = _flush(t, out)
566 out = _flush(t, out)
564
567
565 out = list(out)
568 out = list(out)
566 if out:
569 if out:
567 self._store('\n'.join(out))
570 self._store('\n'.join(out))
568
571
569 def raw_reset(self):
572 def raw_reset(self):
570 """Return raw input only and perform a full reset.
573 """Return raw input only and perform a full reset.
571 """
574 """
572 out = self.source_raw
575 out = self.source_raw
573 self.reset()
576 self.reset()
574 return out
577 return out
575
578
576 def source_reset(self):
579 def source_reset(self):
577 try:
580 try:
578 self.flush_transformers()
581 self.flush_transformers()
579 return self.source
582 return self.source
580 finally:
583 finally:
581 self.reset()
584 self.reset()
582
585
583 def push_accepts_more(self):
586 def push_accepts_more(self):
584 if self.transformer_accumulating:
587 if self.transformer_accumulating:
585 return True
588 return True
586 else:
589 else:
587 return super(IPythonInputSplitter, self).push_accepts_more()
590 return super(IPythonInputSplitter, self).push_accepts_more()
588
591
589 def transform_cell(self, cell):
592 def transform_cell(self, cell):
590 """Process and translate a cell of input.
593 """Process and translate a cell of input.
591 """
594 """
592 self.reset()
595 self.reset()
593 try:
596 try:
594 self.push(cell)
597 self.push(cell)
595 self.flush_transformers()
598 self.flush_transformers()
596 return self.source
599 return self.source
597 finally:
600 finally:
598 self.reset()
601 self.reset()
599
602
600 def push(self, lines):
603 def push(self, lines):
601 """Push one or more lines of IPython input.
604 """Push one or more lines of IPython input.
602
605
603 This stores the given lines and returns a status code indicating
606 This stores the given lines and returns a status code indicating
604 whether the code forms a complete Python block or not, after processing
607 whether the code forms a complete Python block or not, after processing
605 all input lines for special IPython syntax.
608 all input lines for special IPython syntax.
606
609
607 Any exceptions generated in compilation are swallowed, but if an
610 Any exceptions generated in compilation are swallowed, but if an
608 exception was produced, the method returns True.
611 exception was produced, the method returns True.
609
612
610 Parameters
613 Parameters
611 ----------
614 ----------
612 lines : string
615 lines : string
613 One or more lines of Python input.
616 One or more lines of Python input.
614
617
615 Returns
618 Returns
616 -------
619 -------
617 is_complete : boolean
620 is_complete : boolean
618 True if the current input source (the result of the current input
621 True if the current input source (the result of the current input
619 plus prior inputs) forms a complete Python execution block. Note that
622 plus prior inputs) forms a complete Python execution block. Note that
620 this value is also stored as a private attribute (_is_complete), so it
623 this value is also stored as a private attribute (_is_complete), so it
621 can be queried at any time.
624 can be queried at any time.
622 """
625 """
623
626
624 # We must ensure all input is pure unicode
627 # We must ensure all input is pure unicode
625 lines = cast_unicode(lines, self.encoding)
628 lines = cast_unicode(lines, self.encoding)
626
629
627 # ''.splitlines() --> [], but we need to push the empty line to transformers
630 # ''.splitlines() --> [], but we need to push the empty line to transformers
628 lines_list = lines.splitlines()
631 lines_list = lines.splitlines()
629 if not lines_list:
632 if not lines_list:
630 lines_list = ['']
633 lines_list = ['']
631
634
632 # Store raw source before applying any transformations to it. Note
635 # Store raw source before applying any transformations to it. Note
633 # that this must be done *after* the reset() call that would otherwise
636 # that this must be done *after* the reset() call that would otherwise
634 # flush the buffer.
637 # flush the buffer.
635 self._store(lines, self._buffer_raw, 'source_raw')
638 self._store(lines, self._buffer_raw, 'source_raw')
636
639
637 for line in lines_list:
640 for line in lines_list:
638 out = self.push_line(line)
641 out = self.push_line(line)
639
642
640 return out
643 return out
641
644
642 def push_line(self, line):
645 def push_line(self, line):
643 buf = self._buffer
646 buf = self._buffer
644
647
645 def _accumulating(dbg):
648 def _accumulating(dbg):
646 #print(dbg)
649 #print(dbg)
647 self.transformer_accumulating = True
650 self.transformer_accumulating = True
648 return False
651 return False
649
652
650 for transformer in self.physical_line_transforms:
653 for transformer in self.physical_line_transforms:
651 line = transformer.push(line)
654 line = transformer.push(line)
652 if line is None:
655 if line is None:
653 return _accumulating(transformer)
656 return _accumulating(transformer)
654
657
655 if not self.within_python_line:
658 if not self.within_python_line:
656 line = self.assemble_logical_lines.push(line)
659 line = self.assemble_logical_lines.push(line)
657 if line is None:
660 if line is None:
658 return _accumulating('acc logical line')
661 return _accumulating('acc logical line')
659
662
660 for transformer in self.logical_line_transforms:
663 for transformer in self.logical_line_transforms:
661 line = transformer.push(line)
664 line = transformer.push(line)
662 if line is None:
665 if line is None:
663 return _accumulating(transformer)
666 return _accumulating(transformer)
664
667
665 line = self.assemble_python_lines.push(line)
668 line = self.assemble_python_lines.push(line)
666 if line is None:
669 if line is None:
667 self.within_python_line = True
670 self.within_python_line = True
668 return _accumulating('acc python line')
671 return _accumulating('acc python line')
669 else:
672 else:
670 self.within_python_line = False
673 self.within_python_line = False
671
674
672 for transformer in self.python_line_transforms:
675 for transformer in self.python_line_transforms:
673 line = transformer.push(line)
676 line = transformer.push(line)
674 if line is None:
677 if line is None:
675 return _accumulating(transformer)
678 return _accumulating(transformer)
676
679
677 #print("transformers clear") #debug
680 #print("transformers clear") #debug
678 self.transformer_accumulating = False
681 self.transformer_accumulating = False
679 return super(IPythonInputSplitter, self).push(line)
682 return super(IPythonInputSplitter, self).push(line)
@@ -1,604 +1,605 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the inputsplitter module."""
2 """Tests for the inputsplitter module."""
3
3
4 from __future__ import print_function
4 from __future__ import print_function
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import unittest
9 import unittest
10 import sys
10 import sys
11
11
12 import nose.tools as nt
12 import nose.tools as nt
13
13
14 from IPython.core import inputsplitter as isp
14 from IPython.core import inputsplitter as isp
15 from IPython.core.inputtransformer import InputTransformer
15 from IPython.core.inputtransformer import InputTransformer
16 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
16 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
17 from IPython.testing import tools as tt
17 from IPython.testing import tools as tt
18 from IPython.utils import py3compat
18 from IPython.utils import py3compat
19 from IPython.utils.py3compat import string_types, input
19 from IPython.utils.py3compat import string_types, input
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Semi-complete examples (also used as tests)
22 # Semi-complete examples (also used as tests)
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 # Note: at the bottom, there's a slightly more complete version of this that
25 # Note: at the bottom, there's a slightly more complete version of this that
26 # can be useful during development of code here.
26 # can be useful during development of code here.
27
27
28 def mini_interactive_loop(input_func):
28 def mini_interactive_loop(input_func):
29 """Minimal example of the logic of an interactive interpreter loop.
29 """Minimal example of the logic of an interactive interpreter loop.
30
30
31 This serves as an example, and it is used by the test system with a fake
31 This serves as an example, and it is used by the test system with a fake
32 raw_input that simulates interactive input."""
32 raw_input that simulates interactive input."""
33
33
34 from IPython.core.inputsplitter import InputSplitter
34 from IPython.core.inputsplitter import InputSplitter
35
35
36 isp = InputSplitter()
36 isp = InputSplitter()
37 # In practice, this input loop would be wrapped in an outside loop to read
37 # In practice, this input loop would be wrapped in an outside loop to read
38 # input indefinitely, until some exit/quit command was issued. Here we
38 # input indefinitely, until some exit/quit command was issued. Here we
39 # only illustrate the basic inner loop.
39 # only illustrate the basic inner loop.
40 while isp.push_accepts_more():
40 while isp.push_accepts_more():
41 indent = ' '*isp.indent_spaces
41 indent = ' '*isp.indent_spaces
42 prompt = '>>> ' + indent
42 prompt = '>>> ' + indent
43 line = indent + input_func(prompt)
43 line = indent + input_func(prompt)
44 isp.push(line)
44 isp.push(line)
45
45
46 # Here we just return input so we can use it in a test suite, but a real
46 # Here we just return input so we can use it in a test suite, but a real
47 # interpreter would instead send it for execution somewhere.
47 # interpreter would instead send it for execution somewhere.
48 src = isp.source_reset()
48 src = isp.source_reset()
49 #print 'Input source was:\n', src # dbg
49 #print 'Input source was:\n', src # dbg
50 return src
50 return src
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Test utilities, just for local use
53 # Test utilities, just for local use
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 def assemble(block):
56 def assemble(block):
57 """Assemble a block into multi-line sub-blocks."""
57 """Assemble a block into multi-line sub-blocks."""
58 return ['\n'.join(sub_block)+'\n' for sub_block in block]
58 return ['\n'.join(sub_block)+'\n' for sub_block in block]
59
59
60
60
61 def pseudo_input(lines):
61 def pseudo_input(lines):
62 """Return a function that acts like raw_input but feeds the input list."""
62 """Return a function that acts like raw_input but feeds the input list."""
63 ilines = iter(lines)
63 ilines = iter(lines)
64 def raw_in(prompt):
64 def raw_in(prompt):
65 try:
65 try:
66 return next(ilines)
66 return next(ilines)
67 except StopIteration:
67 except StopIteration:
68 return ''
68 return ''
69 return raw_in
69 return raw_in
70
70
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72 # Tests
72 # Tests
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 def test_spaces():
74 def test_spaces():
75 tests = [('', 0),
75 tests = [('', 0),
76 (' ', 1),
76 (' ', 1),
77 ('\n', 0),
77 ('\n', 0),
78 (' \n', 1),
78 (' \n', 1),
79 ('x', 0),
79 ('x', 0),
80 (' x', 1),
80 (' x', 1),
81 (' x',2),
81 (' x',2),
82 (' x',4),
82 (' x',4),
83 # Note: tabs are counted as a single whitespace!
83 # Note: tabs are counted as a single whitespace!
84 ('\tx', 1),
84 ('\tx', 1),
85 ('\t x', 2),
85 ('\t x', 2),
86 ]
86 ]
87 tt.check_pairs(isp.num_ini_spaces, tests)
87 tt.check_pairs(isp.num_ini_spaces, tests)
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 tt.check_pairs(isp.remove_comments, tests)
99 tt.check_pairs(isp.remove_comments, tests)
100
100
101
101
102 def test_get_input_encoding():
102 def test_get_input_encoding():
103 encoding = isp.get_input_encoding()
103 encoding = isp.get_input_encoding()
104 nt.assert_true(isinstance(encoding, string_types))
104 nt.assert_true(isinstance(encoding, string_types))
105 # simple-minded check that at least encoding a simple string works with the
105 # simple-minded check that at least encoding a simple string works with the
106 # encoding we got.
106 # encoding we got.
107 nt.assert_equal(u'test'.encode(encoding), b'test')
107 nt.assert_equal(u'test'.encode(encoding), b'test')
108
108
109
109
110 class NoInputEncodingTestCase(unittest.TestCase):
110 class NoInputEncodingTestCase(unittest.TestCase):
111 def setUp(self):
111 def setUp(self):
112 self.old_stdin = sys.stdin
112 self.old_stdin = sys.stdin
113 class X: pass
113 class X: pass
114 fake_stdin = X()
114 fake_stdin = X()
115 sys.stdin = fake_stdin
115 sys.stdin = fake_stdin
116
116
117 def test(self):
117 def test(self):
118 # Verify that if sys.stdin has no 'encoding' attribute we do the right
118 # Verify that if sys.stdin has no 'encoding' attribute we do the right
119 # thing
119 # thing
120 enc = isp.get_input_encoding()
120 enc = isp.get_input_encoding()
121 self.assertEqual(enc, 'ascii')
121 self.assertEqual(enc, 'ascii')
122
122
123 def tearDown(self):
123 def tearDown(self):
124 sys.stdin = self.old_stdin
124 sys.stdin = self.old_stdin
125
125
126
126
127 class InputSplitterTestCase(unittest.TestCase):
127 class InputSplitterTestCase(unittest.TestCase):
128 def setUp(self):
128 def setUp(self):
129 self.isp = isp.InputSplitter()
129 self.isp = isp.InputSplitter()
130
130
131 def test_reset(self):
131 def test_reset(self):
132 isp = self.isp
132 isp = self.isp
133 isp.push('x=1')
133 isp.push('x=1')
134 isp.reset()
134 isp.reset()
135 self.assertEqual(isp._buffer, [])
135 self.assertEqual(isp._buffer, [])
136 self.assertEqual(isp.indent_spaces, 0)
136 self.assertEqual(isp.indent_spaces, 0)
137 self.assertEqual(isp.source, '')
137 self.assertEqual(isp.source, '')
138 self.assertEqual(isp.code, None)
138 self.assertEqual(isp.code, None)
139 self.assertEqual(isp._is_complete, False)
139 self.assertEqual(isp._is_complete, False)
140
140
141 def test_source(self):
141 def test_source(self):
142 self.isp._store('1')
142 self.isp._store('1')
143 self.isp._store('2')
143 self.isp._store('2')
144 self.assertEqual(self.isp.source, '1\n2\n')
144 self.assertEqual(self.isp.source, '1\n2\n')
145 self.assertTrue(len(self.isp._buffer)>0)
145 self.assertTrue(len(self.isp._buffer)>0)
146 self.assertEqual(self.isp.source_reset(), '1\n2\n')
146 self.assertEqual(self.isp.source_reset(), '1\n2\n')
147 self.assertEqual(self.isp._buffer, [])
147 self.assertEqual(self.isp._buffer, [])
148 self.assertEqual(self.isp.source, '')
148 self.assertEqual(self.isp.source, '')
149
149
150 def test_indent(self):
150 def test_indent(self):
151 isp = self.isp # shorthand
151 isp = self.isp # shorthand
152 isp.push('x=1')
152 isp.push('x=1')
153 self.assertEqual(isp.indent_spaces, 0)
153 self.assertEqual(isp.indent_spaces, 0)
154 isp.push('if 1:\n x=1')
154 isp.push('if 1:\n x=1')
155 self.assertEqual(isp.indent_spaces, 4)
155 self.assertEqual(isp.indent_spaces, 4)
156 isp.push('y=2\n')
156 isp.push('y=2\n')
157 self.assertEqual(isp.indent_spaces, 0)
157 self.assertEqual(isp.indent_spaces, 0)
158
158
159 def test_indent2(self):
159 def test_indent2(self):
160 isp = self.isp
160 isp = self.isp
161 isp.push('if 1:')
161 isp.push('if 1:')
162 self.assertEqual(isp.indent_spaces, 4)
162 self.assertEqual(isp.indent_spaces, 4)
163 isp.push(' x=1')
163 isp.push(' x=1')
164 self.assertEqual(isp.indent_spaces, 4)
164 self.assertEqual(isp.indent_spaces, 4)
165 # Blank lines shouldn't change the indent level
165 # Blank lines shouldn't change the indent level
166 isp.push(' '*2)
166 isp.push(' '*2)
167 self.assertEqual(isp.indent_spaces, 4)
167 self.assertEqual(isp.indent_spaces, 4)
168
168
169 def test_indent3(self):
169 def test_indent3(self):
170 isp = self.isp
170 isp = self.isp
171 # When a multiline statement contains parens or multiline strings, we
171 # When a multiline statement contains parens or multiline strings, we
172 # shouldn't get confused.
172 # shouldn't get confused.
173 isp.push("if 1:")
173 isp.push("if 1:")
174 isp.push(" x = (1+\n 2)")
174 isp.push(" x = (1+\n 2)")
175 self.assertEqual(isp.indent_spaces, 4)
175 self.assertEqual(isp.indent_spaces, 4)
176
176
177 def test_indent4(self):
177 def test_indent4(self):
178 isp = self.isp
178 isp = self.isp
179 # whitespace after ':' should not screw up indent level
179 # whitespace after ':' should not screw up indent level
180 isp.push('if 1: \n x=1')
180 isp.push('if 1: \n x=1')
181 self.assertEqual(isp.indent_spaces, 4)
181 self.assertEqual(isp.indent_spaces, 4)
182 isp.push('y=2\n')
182 isp.push('y=2\n')
183 self.assertEqual(isp.indent_spaces, 0)
183 self.assertEqual(isp.indent_spaces, 0)
184 isp.push('if 1:\t\n x=1')
184 isp.push('if 1:\t\n x=1')
185 self.assertEqual(isp.indent_spaces, 4)
185 self.assertEqual(isp.indent_spaces, 4)
186 isp.push('y=2\n')
186 isp.push('y=2\n')
187 self.assertEqual(isp.indent_spaces, 0)
187 self.assertEqual(isp.indent_spaces, 0)
188
188
189 def test_dedent_pass(self):
189 def test_dedent_pass(self):
190 isp = self.isp # shorthand
190 isp = self.isp # shorthand
191 # should NOT cause dedent
191 # should NOT cause dedent
192 isp.push('if 1:\n passes = 5')
192 isp.push('if 1:\n passes = 5')
193 self.assertEqual(isp.indent_spaces, 4)
193 self.assertEqual(isp.indent_spaces, 4)
194 isp.push('if 1:\n pass')
194 isp.push('if 1:\n pass')
195 self.assertEqual(isp.indent_spaces, 0)
195 self.assertEqual(isp.indent_spaces, 0)
196 isp.push('if 1:\n pass ')
196 isp.push('if 1:\n pass ')
197 self.assertEqual(isp.indent_spaces, 0)
197 self.assertEqual(isp.indent_spaces, 0)
198
198
199 def test_dedent_break(self):
199 def test_dedent_break(self):
200 isp = self.isp # shorthand
200 isp = self.isp # shorthand
201 # should NOT cause dedent
201 # should NOT cause dedent
202 isp.push('while 1:\n breaks = 5')
202 isp.push('while 1:\n breaks = 5')
203 self.assertEqual(isp.indent_spaces, 4)
203 self.assertEqual(isp.indent_spaces, 4)
204 isp.push('while 1:\n break')
204 isp.push('while 1:\n break')
205 self.assertEqual(isp.indent_spaces, 0)
205 self.assertEqual(isp.indent_spaces, 0)
206 isp.push('while 1:\n break ')
206 isp.push('while 1:\n break ')
207 self.assertEqual(isp.indent_spaces, 0)
207 self.assertEqual(isp.indent_spaces, 0)
208
208
209 def test_dedent_continue(self):
209 def test_dedent_continue(self):
210 isp = self.isp # shorthand
210 isp = self.isp # shorthand
211 # should NOT cause dedent
211 # should NOT cause dedent
212 isp.push('while 1:\n continues = 5')
212 isp.push('while 1:\n continues = 5')
213 self.assertEqual(isp.indent_spaces, 4)
213 self.assertEqual(isp.indent_spaces, 4)
214 isp.push('while 1:\n continue')
214 isp.push('while 1:\n continue')
215 self.assertEqual(isp.indent_spaces, 0)
215 self.assertEqual(isp.indent_spaces, 0)
216 isp.push('while 1:\n continue ')
216 isp.push('while 1:\n continue ')
217 self.assertEqual(isp.indent_spaces, 0)
217 self.assertEqual(isp.indent_spaces, 0)
218
218
219 def test_dedent_raise(self):
219 def test_dedent_raise(self):
220 isp = self.isp # shorthand
220 isp = self.isp # shorthand
221 # should NOT cause dedent
221 # should NOT cause dedent
222 isp.push('if 1:\n raised = 4')
222 isp.push('if 1:\n raised = 4')
223 self.assertEqual(isp.indent_spaces, 4)
223 self.assertEqual(isp.indent_spaces, 4)
224 isp.push('if 1:\n raise TypeError()')
224 isp.push('if 1:\n raise TypeError()')
225 self.assertEqual(isp.indent_spaces, 0)
225 self.assertEqual(isp.indent_spaces, 0)
226 isp.push('if 1:\n raise')
226 isp.push('if 1:\n raise')
227 self.assertEqual(isp.indent_spaces, 0)
227 self.assertEqual(isp.indent_spaces, 0)
228 isp.push('if 1:\n raise ')
228 isp.push('if 1:\n raise ')
229 self.assertEqual(isp.indent_spaces, 0)
229 self.assertEqual(isp.indent_spaces, 0)
230
230
231 def test_dedent_return(self):
231 def test_dedent_return(self):
232 isp = self.isp # shorthand
232 isp = self.isp # shorthand
233 # should NOT cause dedent
233 # should NOT cause dedent
234 isp.push('if 1:\n returning = 4')
234 isp.push('if 1:\n returning = 4')
235 self.assertEqual(isp.indent_spaces, 4)
235 self.assertEqual(isp.indent_spaces, 4)
236 isp.push('if 1:\n return 5 + 493')
236 isp.push('if 1:\n return 5 + 493')
237 self.assertEqual(isp.indent_spaces, 0)
237 self.assertEqual(isp.indent_spaces, 0)
238 isp.push('if 1:\n return')
238 isp.push('if 1:\n return')
239 self.assertEqual(isp.indent_spaces, 0)
239 self.assertEqual(isp.indent_spaces, 0)
240 isp.push('if 1:\n return ')
240 isp.push('if 1:\n return ')
241 self.assertEqual(isp.indent_spaces, 0)
241 self.assertEqual(isp.indent_spaces, 0)
242 isp.push('if 1:\n return(0)')
242 isp.push('if 1:\n return(0)')
243 self.assertEqual(isp.indent_spaces, 0)
243 self.assertEqual(isp.indent_spaces, 0)
244
244
245 def test_push(self):
245 def test_push(self):
246 isp = self.isp
246 isp = self.isp
247 self.assertTrue(isp.push('x=1'))
247 self.assertTrue(isp.push('x=1'))
248
248
249 def test_push2(self):
249 def test_push2(self):
250 isp = self.isp
250 isp = self.isp
251 self.assertFalse(isp.push('if 1:'))
251 self.assertFalse(isp.push('if 1:'))
252 for line in [' x=1', '# a comment', ' y=2']:
252 for line in [' x=1', '# a comment', ' y=2']:
253 print(line)
253 print(line)
254 self.assertTrue(isp.push(line))
254 self.assertTrue(isp.push(line))
255
255
256 def test_push3(self):
256 def test_push3(self):
257 isp = self.isp
257 isp = self.isp
258 isp.push('if True:')
258 isp.push('if True:')
259 isp.push(' a = 1')
259 isp.push(' a = 1')
260 self.assertFalse(isp.push('b = [1,'))
260 self.assertFalse(isp.push('b = [1,'))
261
261
262 def test_push_accepts_more(self):
262 def test_push_accepts_more(self):
263 isp = self.isp
263 isp = self.isp
264 isp.push('x=1')
264 isp.push('x=1')
265 self.assertFalse(isp.push_accepts_more())
265 self.assertFalse(isp.push_accepts_more())
266
266
267 def test_push_accepts_more2(self):
267 def test_push_accepts_more2(self):
268 isp = self.isp
268 isp = self.isp
269 isp.push('if 1:')
269 isp.push('if 1:')
270 self.assertTrue(isp.push_accepts_more())
270 self.assertTrue(isp.push_accepts_more())
271 isp.push(' x=1')
271 isp.push(' x=1')
272 self.assertTrue(isp.push_accepts_more())
272 self.assertTrue(isp.push_accepts_more())
273 isp.push('')
273 isp.push('')
274 self.assertFalse(isp.push_accepts_more())
274 self.assertFalse(isp.push_accepts_more())
275
275
276 def test_push_accepts_more3(self):
276 def test_push_accepts_more3(self):
277 isp = self.isp
277 isp = self.isp
278 isp.push("x = (2+\n3)")
278 isp.push("x = (2+\n3)")
279 self.assertFalse(isp.push_accepts_more())
279 self.assertFalse(isp.push_accepts_more())
280
280
281 def test_push_accepts_more4(self):
281 def test_push_accepts_more4(self):
282 isp = self.isp
282 isp = self.isp
283 # When a multiline statement contains parens or multiline strings, we
283 # When a multiline statement contains parens or multiline strings, we
284 # shouldn't get confused.
284 # shouldn't get confused.
285 # FIXME: we should be able to better handle de-dents in statements like
285 # FIXME: we should be able to better handle de-dents in statements like
286 # multiline strings and multiline expressions (continued with \ or
286 # multiline strings and multiline expressions (continued with \ or
287 # parens). Right now we aren't handling the indentation tracking quite
287 # parens). Right now we aren't handling the indentation tracking quite
288 # correctly with this, though in practice it may not be too much of a
288 # correctly with this, though in practice it may not be too much of a
289 # problem. We'll need to see.
289 # problem. We'll need to see.
290 isp.push("if 1:")
290 isp.push("if 1:")
291 isp.push(" x = (2+")
291 isp.push(" x = (2+")
292 isp.push(" 3)")
292 isp.push(" 3)")
293 self.assertTrue(isp.push_accepts_more())
293 self.assertTrue(isp.push_accepts_more())
294 isp.push(" y = 3")
294 isp.push(" y = 3")
295 self.assertTrue(isp.push_accepts_more())
295 self.assertTrue(isp.push_accepts_more())
296 isp.push('')
296 isp.push('')
297 self.assertFalse(isp.push_accepts_more())
297 self.assertFalse(isp.push_accepts_more())
298
298
299 def test_push_accepts_more5(self):
299 def test_push_accepts_more5(self):
300 isp = self.isp
300 isp = self.isp
301 isp.push('try:')
301 isp.push('try:')
302 isp.push(' a = 5')
302 isp.push(' a = 5')
303 isp.push('except:')
303 isp.push('except:')
304 isp.push(' raise')
304 isp.push(' raise')
305 # We want to be able to add an else: block at this point, so it should
305 # We want to be able to add an else: block at this point, so it should
306 # wait for a blank line.
306 # wait for a blank line.
307 self.assertTrue(isp.push_accepts_more())
307 self.assertTrue(isp.push_accepts_more())
308
308
309 def test_continuation(self):
309 def test_continuation(self):
310 isp = self.isp
310 isp = self.isp
311 isp.push("import os, \\")
311 isp.push("import os, \\")
312 self.assertTrue(isp.push_accepts_more())
312 self.assertTrue(isp.push_accepts_more())
313 isp.push("sys")
313 isp.push("sys")
314 self.assertFalse(isp.push_accepts_more())
314 self.assertFalse(isp.push_accepts_more())
315
315
316 def test_syntax_error(self):
316 def test_syntax_error(self):
317 isp = self.isp
317 isp = self.isp
318 # Syntax errors immediately produce a 'ready' block, so the invalid
318 # Syntax errors immediately produce a 'ready' block, so the invalid
319 # Python can be sent to the kernel for evaluation with possible ipython
319 # Python can be sent to the kernel for evaluation with possible ipython
320 # special-syntax conversion.
320 # special-syntax conversion.
321 isp.push('run foo')
321 isp.push('run foo')
322 self.assertFalse(isp.push_accepts_more())
322 self.assertFalse(isp.push_accepts_more())
323
323
324 def test_unicode(self):
324 def test_unicode(self):
325 self.isp.push(u"PΓ©rez")
325 self.isp.push(u"PΓ©rez")
326 self.isp.push(u'\xc3\xa9')
326 self.isp.push(u'\xc3\xa9')
327 self.isp.push(u"u'\xc3\xa9'")
327 self.isp.push(u"u'\xc3\xa9'")
328
328
329 def test_line_continuation(self):
329 def test_line_continuation(self):
330 """ Test issue #2108."""
330 """ Test issue #2108."""
331 isp = self.isp
331 isp = self.isp
332 # A blank line after a line continuation should not accept more
332 # A blank line after a line continuation should not accept more
333 isp.push("1 \\\n\n")
333 isp.push("1 \\\n\n")
334 self.assertFalse(isp.push_accepts_more())
334 self.assertFalse(isp.push_accepts_more())
335 # Whitespace after a \ is a SyntaxError. The only way to test that
335 # Whitespace after a \ is a SyntaxError. The only way to test that
336 # here is to test that push doesn't accept more (as with
336 # here is to test that push doesn't accept more (as with
337 # test_syntax_error() above).
337 # test_syntax_error() above).
338 isp.push(r"1 \ ")
338 isp.push(r"1 \ ")
339 self.assertFalse(isp.push_accepts_more())
339 self.assertFalse(isp.push_accepts_more())
340 # Even if the line is continuable (c.f. the regular Python
340 # Even if the line is continuable (c.f. the regular Python
341 # interpreter)
341 # interpreter)
342 isp.push(r"(1 \ ")
342 isp.push(r"(1 \ ")
343 self.assertFalse(isp.push_accepts_more())
343 self.assertFalse(isp.push_accepts_more())
344
344
345 def test_check_complete(self):
345 def test_check_complete(self):
346 isp = self.isp
346 isp = self.isp
347 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
347 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
348 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
348 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
349 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
349 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
350 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
350 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
351 self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
351
352
352 class InteractiveLoopTestCase(unittest.TestCase):
353 class InteractiveLoopTestCase(unittest.TestCase):
353 """Tests for an interactive loop like a python shell.
354 """Tests for an interactive loop like a python shell.
354 """
355 """
355 def check_ns(self, lines, ns):
356 def check_ns(self, lines, ns):
356 """Validate that the given input lines produce the resulting namespace.
357 """Validate that the given input lines produce the resulting namespace.
357
358
358 Note: the input lines are given exactly as they would be typed in an
359 Note: the input lines are given exactly as they would be typed in an
359 auto-indenting environment, as mini_interactive_loop above already does
360 auto-indenting environment, as mini_interactive_loop above already does
360 auto-indenting and prepends spaces to the input.
361 auto-indenting and prepends spaces to the input.
361 """
362 """
362 src = mini_interactive_loop(pseudo_input(lines))
363 src = mini_interactive_loop(pseudo_input(lines))
363 test_ns = {}
364 test_ns = {}
364 exec(src, test_ns)
365 exec(src, test_ns)
365 # We can't check that the provided ns is identical to the test_ns,
366 # We can't check that the provided ns is identical to the test_ns,
366 # because Python fills test_ns with extra keys (copyright, etc). But
367 # because Python fills test_ns with extra keys (copyright, etc). But
367 # we can check that the given dict is *contained* in test_ns
368 # we can check that the given dict is *contained* in test_ns
368 for k,v in ns.items():
369 for k,v in ns.items():
369 self.assertEqual(test_ns[k], v)
370 self.assertEqual(test_ns[k], v)
370
371
371 def test_simple(self):
372 def test_simple(self):
372 self.check_ns(['x=1'], dict(x=1))
373 self.check_ns(['x=1'], dict(x=1))
373
374
374 def test_simple2(self):
375 def test_simple2(self):
375 self.check_ns(['if 1:', 'x=2'], dict(x=2))
376 self.check_ns(['if 1:', 'x=2'], dict(x=2))
376
377
377 def test_xy(self):
378 def test_xy(self):
378 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
379 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
379
380
380 def test_abc(self):
381 def test_abc(self):
381 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
382 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
382
383
383 def test_multi(self):
384 def test_multi(self):
384 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
385 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
385
386
386
387
387 class IPythonInputTestCase(InputSplitterTestCase):
388 class IPythonInputTestCase(InputSplitterTestCase):
388 """By just creating a new class whose .isp is a different instance, we
389 """By just creating a new class whose .isp is a different instance, we
389 re-run the same test battery on the new input splitter.
390 re-run the same test battery on the new input splitter.
390
391
391 In addition, this runs the tests over the syntax and syntax_ml dicts that
392 In addition, this runs the tests over the syntax and syntax_ml dicts that
392 were tested by individual functions, as part of the OO interface.
393 were tested by individual functions, as part of the OO interface.
393
394
394 It also makes some checks on the raw buffer storage.
395 It also makes some checks on the raw buffer storage.
395 """
396 """
396
397
397 def setUp(self):
398 def setUp(self):
398 self.isp = isp.IPythonInputSplitter()
399 self.isp = isp.IPythonInputSplitter()
399
400
400 def test_syntax(self):
401 def test_syntax(self):
401 """Call all single-line syntax tests from the main object"""
402 """Call all single-line syntax tests from the main object"""
402 isp = self.isp
403 isp = self.isp
403 for example in syntax.values():
404 for example in syntax.values():
404 for raw, out_t in example:
405 for raw, out_t in example:
405 if raw.startswith(' '):
406 if raw.startswith(' '):
406 continue
407 continue
407
408
408 isp.push(raw+'\n')
409 isp.push(raw+'\n')
409 out_raw = isp.source_raw
410 out_raw = isp.source_raw
410 out = isp.source_reset()
411 out = isp.source_reset()
411 self.assertEqual(out.rstrip(), out_t,
412 self.assertEqual(out.rstrip(), out_t,
412 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
413 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
413 self.assertEqual(out_raw.rstrip(), raw.rstrip())
414 self.assertEqual(out_raw.rstrip(), raw.rstrip())
414
415
415 def test_syntax_multiline(self):
416 def test_syntax_multiline(self):
416 isp = self.isp
417 isp = self.isp
417 for example in syntax_ml.values():
418 for example in syntax_ml.values():
418 for line_pairs in example:
419 for line_pairs in example:
419 out_t_parts = []
420 out_t_parts = []
420 raw_parts = []
421 raw_parts = []
421 for lraw, out_t_part in line_pairs:
422 for lraw, out_t_part in line_pairs:
422 if out_t_part is not None:
423 if out_t_part is not None:
423 out_t_parts.append(out_t_part)
424 out_t_parts.append(out_t_part)
424
425
425 if lraw is not None:
426 if lraw is not None:
426 isp.push(lraw)
427 isp.push(lraw)
427 raw_parts.append(lraw)
428 raw_parts.append(lraw)
428
429
429 out_raw = isp.source_raw
430 out_raw = isp.source_raw
430 out = isp.source_reset()
431 out = isp.source_reset()
431 out_t = '\n'.join(out_t_parts).rstrip()
432 out_t = '\n'.join(out_t_parts).rstrip()
432 raw = '\n'.join(raw_parts).rstrip()
433 raw = '\n'.join(raw_parts).rstrip()
433 self.assertEqual(out.rstrip(), out_t)
434 self.assertEqual(out.rstrip(), out_t)
434 self.assertEqual(out_raw.rstrip(), raw)
435 self.assertEqual(out_raw.rstrip(), raw)
435
436
436 def test_syntax_multiline_cell(self):
437 def test_syntax_multiline_cell(self):
437 isp = self.isp
438 isp = self.isp
438 for example in syntax_ml.values():
439 for example in syntax_ml.values():
439
440
440 out_t_parts = []
441 out_t_parts = []
441 for line_pairs in example:
442 for line_pairs in example:
442 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
443 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
443 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
444 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
444 out = isp.transform_cell(raw)
445 out = isp.transform_cell(raw)
445 # Match ignoring trailing whitespace
446 # Match ignoring trailing whitespace
446 self.assertEqual(out.rstrip(), out_t.rstrip())
447 self.assertEqual(out.rstrip(), out_t.rstrip())
447
448
448 def test_cellmagic_preempt(self):
449 def test_cellmagic_preempt(self):
449 isp = self.isp
450 isp = self.isp
450 for raw, name, line, cell in [
451 for raw, name, line, cell in [
451 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
452 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
452 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
453 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
453 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
454 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
454 ("%%cellm \n>>> hi", u'cellm', u'', u'hi'),
455 ("%%cellm \n>>> hi", u'cellm', u'', u'hi'),
455 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
456 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
456 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
457 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
457 ]:
458 ]:
458 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
459 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
459 name, line, cell
460 name, line, cell
460 )
461 )
461 out = isp.transform_cell(raw)
462 out = isp.transform_cell(raw)
462 self.assertEqual(out.rstrip(), expected.rstrip())
463 self.assertEqual(out.rstrip(), expected.rstrip())
463
464
464 def test_multiline_passthrough(self):
465 def test_multiline_passthrough(self):
465 isp = self.isp
466 isp = self.isp
466 class CommentTransformer(InputTransformer):
467 class CommentTransformer(InputTransformer):
467 def __init__(self):
468 def __init__(self):
468 self._lines = []
469 self._lines = []
469
470
470 def push(self, line):
471 def push(self, line):
471 self._lines.append(line + '#')
472 self._lines.append(line + '#')
472
473
473 def reset(self):
474 def reset(self):
474 text = '\n'.join(self._lines)
475 text = '\n'.join(self._lines)
475 self._lines = []
476 self._lines = []
476 return text
477 return text
477
478
478 isp.physical_line_transforms.insert(0, CommentTransformer())
479 isp.physical_line_transforms.insert(0, CommentTransformer())
479
480
480 for raw, expected in [
481 for raw, expected in [
481 ("a=5", "a=5#"),
482 ("a=5", "a=5#"),
482 ("%ls foo", "get_ipython().magic(%r)" % u'ls foo#'),
483 ("%ls foo", "get_ipython().magic(%r)" % u'ls foo#'),
483 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().magic(%r)" % (
484 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().magic(%r)" % (
484 u'ls foo#', u'ls bar#'
485 u'ls foo#', u'ls bar#'
485 )),
486 )),
486 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().magic(%r)\n4#\n5#" % u'ls foo#'),
487 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().magic(%r)\n4#\n5#" % u'ls foo#'),
487 ]:
488 ]:
488 out = isp.transform_cell(raw)
489 out = isp.transform_cell(raw)
489 self.assertEqual(out.rstrip(), expected.rstrip())
490 self.assertEqual(out.rstrip(), expected.rstrip())
490
491
491 #-----------------------------------------------------------------------------
492 #-----------------------------------------------------------------------------
492 # Main - use as a script, mostly for developer experiments
493 # Main - use as a script, mostly for developer experiments
493 #-----------------------------------------------------------------------------
494 #-----------------------------------------------------------------------------
494
495
495 if __name__ == '__main__':
496 if __name__ == '__main__':
496 # A simple demo for interactive experimentation. This code will not get
497 # A simple demo for interactive experimentation. This code will not get
497 # picked up by any test suite.
498 # picked up by any test suite.
498 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
499 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
499
500
500 # configure here the syntax to use, prompt and whether to autoindent
501 # configure here the syntax to use, prompt and whether to autoindent
501 #isp, start_prompt = InputSplitter(), '>>> '
502 #isp, start_prompt = InputSplitter(), '>>> '
502 isp, start_prompt = IPythonInputSplitter(), 'In> '
503 isp, start_prompt = IPythonInputSplitter(), 'In> '
503
504
504 autoindent = True
505 autoindent = True
505 #autoindent = False
506 #autoindent = False
506
507
507 try:
508 try:
508 while True:
509 while True:
509 prompt = start_prompt
510 prompt = start_prompt
510 while isp.push_accepts_more():
511 while isp.push_accepts_more():
511 indent = ' '*isp.indent_spaces
512 indent = ' '*isp.indent_spaces
512 if autoindent:
513 if autoindent:
513 line = indent + input(prompt+indent)
514 line = indent + input(prompt+indent)
514 else:
515 else:
515 line = input(prompt)
516 line = input(prompt)
516 isp.push(line)
517 isp.push(line)
517 prompt = '... '
518 prompt = '... '
518
519
519 # Here we just return input so we can use it in a test suite, but a
520 # Here we just return input so we can use it in a test suite, but a
520 # real interpreter would instead send it for execution somewhere.
521 # real interpreter would instead send it for execution somewhere.
521 #src = isp.source; raise EOFError # dbg
522 #src = isp.source; raise EOFError # dbg
522 raw = isp.source_raw
523 raw = isp.source_raw
523 src = isp.source_reset()
524 src = isp.source_reset()
524 print('Input source was:\n', src)
525 print('Input source was:\n', src)
525 print('Raw source was:\n', raw)
526 print('Raw source was:\n', raw)
526 except EOFError:
527 except EOFError:
527 print('Bye')
528 print('Bye')
528
529
529 # Tests for cell magics support
530 # Tests for cell magics support
530
531
531 def test_last_blank():
532 def test_last_blank():
532 nt.assert_false(isp.last_blank(''))
533 nt.assert_false(isp.last_blank(''))
533 nt.assert_false(isp.last_blank('abc'))
534 nt.assert_false(isp.last_blank('abc'))
534 nt.assert_false(isp.last_blank('abc\n'))
535 nt.assert_false(isp.last_blank('abc\n'))
535 nt.assert_false(isp.last_blank('abc\na'))
536 nt.assert_false(isp.last_blank('abc\na'))
536
537
537 nt.assert_true(isp.last_blank('\n'))
538 nt.assert_true(isp.last_blank('\n'))
538 nt.assert_true(isp.last_blank('\n '))
539 nt.assert_true(isp.last_blank('\n '))
539 nt.assert_true(isp.last_blank('abc\n '))
540 nt.assert_true(isp.last_blank('abc\n '))
540 nt.assert_true(isp.last_blank('abc\n\n'))
541 nt.assert_true(isp.last_blank('abc\n\n'))
541 nt.assert_true(isp.last_blank('abc\nd\n\n'))
542 nt.assert_true(isp.last_blank('abc\nd\n\n'))
542 nt.assert_true(isp.last_blank('abc\nd\ne\n\n'))
543 nt.assert_true(isp.last_blank('abc\nd\ne\n\n'))
543 nt.assert_true(isp.last_blank('abc \n \n \n\n'))
544 nt.assert_true(isp.last_blank('abc \n \n \n\n'))
544
545
545
546
546 def test_last_two_blanks():
547 def test_last_two_blanks():
547 nt.assert_false(isp.last_two_blanks(''))
548 nt.assert_false(isp.last_two_blanks(''))
548 nt.assert_false(isp.last_two_blanks('abc'))
549 nt.assert_false(isp.last_two_blanks('abc'))
549 nt.assert_false(isp.last_two_blanks('abc\n'))
550 nt.assert_false(isp.last_two_blanks('abc\n'))
550 nt.assert_false(isp.last_two_blanks('abc\n\na'))
551 nt.assert_false(isp.last_two_blanks('abc\n\na'))
551 nt.assert_false(isp.last_two_blanks('abc\n \n'))
552 nt.assert_false(isp.last_two_blanks('abc\n \n'))
552 nt.assert_false(isp.last_two_blanks('abc\n\n'))
553 nt.assert_false(isp.last_two_blanks('abc\n\n'))
553
554
554 nt.assert_true(isp.last_two_blanks('\n\n'))
555 nt.assert_true(isp.last_two_blanks('\n\n'))
555 nt.assert_true(isp.last_two_blanks('\n\n '))
556 nt.assert_true(isp.last_two_blanks('\n\n '))
556 nt.assert_true(isp.last_two_blanks('\n \n'))
557 nt.assert_true(isp.last_two_blanks('\n \n'))
557 nt.assert_true(isp.last_two_blanks('abc\n\n '))
558 nt.assert_true(isp.last_two_blanks('abc\n\n '))
558 nt.assert_true(isp.last_two_blanks('abc\n\n\n'))
559 nt.assert_true(isp.last_two_blanks('abc\n\n\n'))
559 nt.assert_true(isp.last_two_blanks('abc\n\n \n'))
560 nt.assert_true(isp.last_two_blanks('abc\n\n \n'))
560 nt.assert_true(isp.last_two_blanks('abc\n\n \n '))
561 nt.assert_true(isp.last_two_blanks('abc\n\n \n '))
561 nt.assert_true(isp.last_two_blanks('abc\n\n \n \n'))
562 nt.assert_true(isp.last_two_blanks('abc\n\n \n \n'))
562 nt.assert_true(isp.last_two_blanks('abc\nd\n\n\n'))
563 nt.assert_true(isp.last_two_blanks('abc\nd\n\n\n'))
563 nt.assert_true(isp.last_two_blanks('abc\nd\ne\nf\n\n\n'))
564 nt.assert_true(isp.last_two_blanks('abc\nd\ne\nf\n\n\n'))
564
565
565
566
566 class CellMagicsCommon(object):
567 class CellMagicsCommon(object):
567
568
568 def test_whole_cell(self):
569 def test_whole_cell(self):
569 src = "%%cellm line\nbody\n"
570 src = "%%cellm line\nbody\n"
570 out = self.sp.transform_cell(src)
571 out = self.sp.transform_cell(src)
571 ref = u"get_ipython().run_cell_magic({u}'cellm', {u}'line', {u}'body')\n"
572 ref = u"get_ipython().run_cell_magic({u}'cellm', {u}'line', {u}'body')\n"
572 nt.assert_equal(out, py3compat.u_format(ref))
573 nt.assert_equal(out, py3compat.u_format(ref))
573
574
574 def test_cellmagic_help(self):
575 def test_cellmagic_help(self):
575 self.sp.push('%%cellm?')
576 self.sp.push('%%cellm?')
576 nt.assert_false(self.sp.push_accepts_more())
577 nt.assert_false(self.sp.push_accepts_more())
577
578
578 def tearDown(self):
579 def tearDown(self):
579 self.sp.reset()
580 self.sp.reset()
580
581
581
582
582 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
583 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
583 sp = isp.IPythonInputSplitter(line_input_checker=False)
584 sp = isp.IPythonInputSplitter(line_input_checker=False)
584
585
585 def test_incremental(self):
586 def test_incremental(self):
586 sp = self.sp
587 sp = self.sp
587 sp.push('%%cellm firstline\n')
588 sp.push('%%cellm firstline\n')
588 nt.assert_true(sp.push_accepts_more()) #1
589 nt.assert_true(sp.push_accepts_more()) #1
589 sp.push('line2\n')
590 sp.push('line2\n')
590 nt.assert_true(sp.push_accepts_more()) #2
591 nt.assert_true(sp.push_accepts_more()) #2
591 sp.push('\n')
592 sp.push('\n')
592 # This should accept a blank line and carry on until the cell is reset
593 # This should accept a blank line and carry on until the cell is reset
593 nt.assert_true(sp.push_accepts_more()) #3
594 nt.assert_true(sp.push_accepts_more()) #3
594
595
595 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
596 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
596 sp = isp.IPythonInputSplitter(line_input_checker=True)
597 sp = isp.IPythonInputSplitter(line_input_checker=True)
597
598
598 def test_incremental(self):
599 def test_incremental(self):
599 sp = self.sp
600 sp = self.sp
600 sp.push('%%cellm line2\n')
601 sp.push('%%cellm line2\n')
601 nt.assert_true(sp.push_accepts_more()) #1
602 nt.assert_true(sp.push_accepts_more()) #1
602 sp.push('\n')
603 sp.push('\n')
603 # In this case, a blank line should end the cell magic
604 # In this case, a blank line should end the cell magic
604 nt.assert_false(sp.push_accepts_more()) #2
605 nt.assert_false(sp.push_accepts_more()) #2
General Comments 0
You need to be logged in to leave comments. Login now