##// END OF EJS Templates
Fix for \ at end of comment, and add tests
Thomas Kluyver -
Show More
@@ -1,712 +1,713 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 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
10
11 For more details, see the class docstring below.
11 For more details, see the class docstring below.
12
12
13 Syntax Transformations
13 Syntax Transformations
14 ----------------------
14 ----------------------
15
15
16 One of the main jobs of the code in this file is to apply all syntax
16 One of the main jobs of the code in this file is to apply all syntax
17 transformations that make up 'the IPython language', i.e. magics, shell
17 transformations that make up 'the IPython language', i.e. magics, shell
18 escapes, etc. All transformations should be implemented as *fully stateless*
18 escapes, etc. All transformations should be implemented as *fully stateless*
19 entities, that simply take one line as their input and return a line.
19 entities, that simply take one line as their input and return a line.
20 Internally for implementation purposes they may be a normal function or a
20 Internally for implementation purposes they may be a normal function or a
21 callable object, but the only input they receive will be a single line and they
21 callable object, but the only input they receive will be a single line and they
22 should only return a line, without holding any data-dependent state between
22 should only return a line, without holding any data-dependent state between
23 calls.
23 calls.
24
24
25 As an example, the EscapedTransformer is a class so we can more clearly group
25 As an example, the EscapedTransformer is a class so we can more clearly group
26 together the functionality of dispatching to individual functions based on the
26 together the functionality of dispatching to individual functions based on the
27 starting escape character, but the only method for public use is its call
27 starting escape character, but the only method for public use is its call
28 method.
28 method.
29
29
30
30
31 ToDo
31 ToDo
32 ----
32 ----
33
33
34 - Should we make push() actually raise an exception once push_accepts_more()
34 - Should we make push() actually raise an exception once push_accepts_more()
35 returns False?
35 returns False?
36
36
37 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
37 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
38 at least just attributes of a class so not really very exposed.
38 at least just attributes of a class so not really very exposed.
39
39
40 - Think about the best way to support dynamic things: automagic, autocall,
40 - Think about the best way to support dynamic things: automagic, autocall,
41 macros, etc.
41 macros, etc.
42
42
43 - Think of a better heuristic for the application of the transforms in
43 - Think of a better heuristic for the application of the transforms in
44 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
44 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
45 track indentation change events (indent, dedent, nothing) and apply them only
45 track indentation change events (indent, dedent, nothing) and apply them only
46 if the indentation went up, but not otherwise.
46 if the indentation went up, but not otherwise.
47
47
48 - Think of the cleanest way for supporting user-specified transformations (the
48 - Think of the cleanest way for supporting user-specified transformations (the
49 user prefilters we had before).
49 user prefilters we had before).
50
50
51 Authors
51 Authors
52 -------
52 -------
53
53
54 * Fernando Perez
54 * Fernando Perez
55 * Brian Granger
55 * Brian Granger
56 """
56 """
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Copyright (C) 2010 The IPython Development Team
58 # Copyright (C) 2010 The IPython Development Team
59 #
59 #
60 # Distributed under the terms of the BSD License. The full license is in
60 # Distributed under the terms of the BSD License. The full license is in
61 # the file COPYING, distributed as part of this software.
61 # the file COPYING, distributed as part of this software.
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Imports
65 # Imports
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67 # stdlib
67 # stdlib
68 import ast
68 import ast
69 import codeop
69 import codeop
70 import re
70 import re
71 import sys
71 import sys
72
72
73 # IPython modules
73 # IPython modules
74 from IPython.core.splitinput import split_user_input, LineInfo
74 from IPython.core.splitinput import split_user_input, LineInfo
75 from IPython.utils.py3compat import cast_unicode
75 from IPython.utils.py3compat import cast_unicode
76 from IPython.core.inputtransformer import (leading_indent,
76 from IPython.core.inputtransformer import (leading_indent,
77 classic_prompt,
77 classic_prompt,
78 ipy_prompt,
78 ipy_prompt,
79 cellmagic,
79 cellmagic,
80 assemble_logical_lines,
80 assemble_logical_lines,
81 help_end,
81 help_end,
82 escaped_commands,
82 escaped_commands,
83 assign_from_magic,
83 assign_from_magic,
84 assign_from_system,
84 assign_from_system,
85 assemble_python_lines,
85 assemble_python_lines,
86 )
86 )
87
87
88 # Temporary!
88 # Temporary!
89 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
89 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
90 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
90 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
91 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
91 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
92
92
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94 # Utilities
94 # Utilities
95 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
96
96
97 # FIXME: These are general-purpose utilities that later can be moved to the
97 # FIXME: These are general-purpose utilities that later can be moved to the
98 # general ward. Kept here for now because we're being very strict about test
98 # general ward. Kept here for now because we're being very strict about test
99 # coverage with this code, and this lets us ensure that we keep 100% coverage
99 # coverage with this code, and this lets us ensure that we keep 100% coverage
100 # while developing.
100 # while developing.
101
101
102 # compiled regexps for autoindent management
102 # compiled regexps for autoindent management
103 dedent_re = re.compile('|'.join([
103 dedent_re = re.compile('|'.join([
104 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
104 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
105 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
105 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
106 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
106 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
107 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
107 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
108 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
108 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
109 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
109 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
110 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
110 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
111 ]))
111 ]))
112 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
112 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
113
113
114 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
114 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
115 # before pure comments
115 # before pure comments
116 comment_line_re = re.compile('^\s*\#')
116 comment_line_re = re.compile('^\s*\#')
117
117
118
118
119 def num_ini_spaces(s):
119 def num_ini_spaces(s):
120 """Return the number of initial spaces in a string.
120 """Return the number of initial spaces in a string.
121
121
122 Note that tabs are counted as a single space. For now, we do *not* support
122 Note that tabs are counted as a single space. For now, we do *not* support
123 mixing of tabs and spaces in the user's input.
123 mixing of tabs and spaces in the user's input.
124
124
125 Parameters
125 Parameters
126 ----------
126 ----------
127 s : string
127 s : string
128
128
129 Returns
129 Returns
130 -------
130 -------
131 n : int
131 n : int
132 """
132 """
133
133
134 ini_spaces = ini_spaces_re.match(s)
134 ini_spaces = ini_spaces_re.match(s)
135 if ini_spaces:
135 if ini_spaces:
136 return ini_spaces.end()
136 return ini_spaces.end()
137 else:
137 else:
138 return 0
138 return 0
139
139
140 def last_blank(src):
140 def last_blank(src):
141 """Determine if the input source ends in a blank.
141 """Determine if the input source ends in a blank.
142
142
143 A blank is either a newline or a line consisting of whitespace.
143 A blank is either a newline or a line consisting of whitespace.
144
144
145 Parameters
145 Parameters
146 ----------
146 ----------
147 src : string
147 src : string
148 A single or multiline string.
148 A single or multiline string.
149 """
149 """
150 if not src: return False
150 if not src: return False
151 ll = src.splitlines()[-1]
151 ll = src.splitlines()[-1]
152 return (ll == '') or ll.isspace()
152 return (ll == '') or ll.isspace()
153
153
154
154
155 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
155 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
156 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
156 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
157
157
158 def last_two_blanks(src):
158 def last_two_blanks(src):
159 """Determine if the input source ends in two blanks.
159 """Determine if the input source ends in two blanks.
160
160
161 A blank is either a newline or a line consisting of whitespace.
161 A blank is either a newline or a line consisting of whitespace.
162
162
163 Parameters
163 Parameters
164 ----------
164 ----------
165 src : string
165 src : string
166 A single or multiline string.
166 A single or multiline string.
167 """
167 """
168 if not src: return False
168 if not src: return False
169 # The logic here is tricky: I couldn't get a regexp to work and pass all
169 # The logic here is tricky: I couldn't get a regexp to work and pass all
170 # the tests, so I took a different approach: split the source by lines,
170 # the tests, so I took a different approach: split the source by lines,
171 # grab the last two and prepend '###\n' as a stand-in for whatever was in
171 # grab the last two and prepend '###\n' as a stand-in for whatever was in
172 # the body before the last two lines. Then, with that structure, it's
172 # the body before the last two lines. Then, with that structure, it's
173 # possible to analyze with two regexps. Not the most elegant solution, but
173 # possible to analyze with two regexps. Not the most elegant solution, but
174 # it works. If anyone tries to change this logic, make sure to validate
174 # it works. If anyone tries to change this logic, make sure to validate
175 # the whole test suite first!
175 # the whole test suite first!
176 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
176 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
177 return (bool(last_two_blanks_re.match(new_src)) or
177 return (bool(last_two_blanks_re.match(new_src)) or
178 bool(last_two_blanks_re2.match(new_src)) )
178 bool(last_two_blanks_re2.match(new_src)) )
179
179
180
180
181 def remove_comments(src):
181 def remove_comments(src):
182 """Remove all comments from input source.
182 """Remove all comments from input source.
183
183
184 Note: comments are NOT recognized inside of strings!
184 Note: comments are NOT recognized inside of strings!
185
185
186 Parameters
186 Parameters
187 ----------
187 ----------
188 src : string
188 src : string
189 A single or multiline input string.
189 A single or multiline input string.
190
190
191 Returns
191 Returns
192 -------
192 -------
193 String with all Python comments removed.
193 String with all Python comments removed.
194 """
194 """
195
195
196 return re.sub('#.*', '', src)
196 return re.sub('#.*', '', src)
197
197
198
198
199 def get_input_encoding():
199 def get_input_encoding():
200 """Return the default standard input encoding.
200 """Return the default standard input encoding.
201
201
202 If sys.stdin has no encoding, 'ascii' is returned."""
202 If sys.stdin has no encoding, 'ascii' is returned."""
203 # There are strange environments for which sys.stdin.encoding is None. We
203 # There are strange environments for which sys.stdin.encoding is None. We
204 # ensure that a valid encoding is returned.
204 # ensure that a valid encoding is returned.
205 encoding = getattr(sys.stdin, 'encoding', None)
205 encoding = getattr(sys.stdin, 'encoding', None)
206 if encoding is None:
206 if encoding is None:
207 encoding = 'ascii'
207 encoding = 'ascii'
208 return encoding
208 return encoding
209
209
210 #-----------------------------------------------------------------------------
210 #-----------------------------------------------------------------------------
211 # Classes and functions for normal Python syntax handling
211 # Classes and functions for normal Python syntax handling
212 #-----------------------------------------------------------------------------
212 #-----------------------------------------------------------------------------
213
213
214 class InputSplitter(object):
214 class InputSplitter(object):
215 """An object that can accumulate lines of Python source before execution.
215 """An object that can accumulate lines of Python source before execution.
216
216
217 This object is designed to be fed python source line-by-line, using
217 This object is designed to be fed python source line-by-line, using
218 :meth:`push`. It will return on each push whether the currently pushed
218 :meth:`push`. It will return on each push whether the currently pushed
219 code could be executed already. In addition, it provides a method called
219 code could be executed already. In addition, it provides a method called
220 :meth:`push_accepts_more` that can be used to query whether more input
220 :meth:`push_accepts_more` that can be used to query whether more input
221 can be pushed into a single interactive block.
221 can be pushed into a single interactive block.
222
222
223 This is a simple example of how an interactive terminal-based client can use
223 This is a simple example of how an interactive terminal-based client can use
224 this tool::
224 this tool::
225
225
226 isp = InputSplitter()
226 isp = InputSplitter()
227 while isp.push_accepts_more():
227 while isp.push_accepts_more():
228 indent = ' '*isp.indent_spaces
228 indent = ' '*isp.indent_spaces
229 prompt = '>>> ' + indent
229 prompt = '>>> ' + indent
230 line = indent + raw_input(prompt)
230 line = indent + raw_input(prompt)
231 isp.push(line)
231 isp.push(line)
232 print 'Input source was:\n', isp.source_reset(),
232 print 'Input source was:\n', isp.source_reset(),
233 """
233 """
234 # Number of spaces of indentation computed from input that has been pushed
234 # Number of spaces of indentation computed from input that has been pushed
235 # so far. This is the attributes callers should query to get the current
235 # so far. This is the attributes callers should query to get the current
236 # indentation level, in order to provide auto-indent facilities.
236 # indentation level, in order to provide auto-indent facilities.
237 indent_spaces = 0
237 indent_spaces = 0
238 # String, indicating the default input encoding. It is computed by default
238 # String, indicating the default input encoding. It is computed by default
239 # at initialization time via get_input_encoding(), but it can be reset by a
239 # at initialization time via get_input_encoding(), but it can be reset by a
240 # client with specific knowledge of the encoding.
240 # client with specific knowledge of the encoding.
241 encoding = ''
241 encoding = ''
242 # String where the current full source input is stored, properly encoded.
242 # String where the current full source input is stored, properly encoded.
243 # Reading this attribute is the normal way of querying the currently pushed
243 # Reading this attribute is the normal way of querying the currently pushed
244 # source code, that has been properly encoded.
244 # source code, that has been properly encoded.
245 source = ''
245 source = ''
246 # Code object corresponding to the current source. It is automatically
246 # Code object corresponding to the current source. It is automatically
247 # synced to the source, so it can be queried at any time to obtain the code
247 # synced to the source, so it can be queried at any time to obtain the code
248 # object; it will be None if the source doesn't compile to valid Python.
248 # object; it will be None if the source doesn't compile to valid Python.
249 code = None
249 code = None
250 # Input mode
250 # Input mode
251 input_mode = 'line'
251 input_mode = 'line'
252
252
253 # Private attributes
253 # Private attributes
254
254
255 # List with lines of input accumulated so far
255 # List with lines of input accumulated so far
256 _buffer = None
256 _buffer = None
257 # Command compiler
257 # Command compiler
258 _compile = None
258 _compile = None
259 # Mark when input has changed indentation all the way back to flush-left
259 # Mark when input has changed indentation all the way back to flush-left
260 _full_dedent = False
260 _full_dedent = False
261 # Boolean indicating whether the current block is complete
261 # Boolean indicating whether the current block is complete
262 _is_complete = None
262 _is_complete = None
263
263
264 def __init__(self, input_mode=None):
264 def __init__(self, input_mode=None):
265 """Create a new InputSplitter instance.
265 """Create a new InputSplitter instance.
266
266
267 Parameters
267 Parameters
268 ----------
268 ----------
269 input_mode : str
269 input_mode : str
270
270
271 One of ['line', 'cell']; default is 'line'.
271 One of ['line', 'cell']; default is 'line'.
272
272
273 The input_mode parameter controls how new inputs are used when fed via
273 The input_mode parameter controls how new inputs are used when fed via
274 the :meth:`push` method:
274 the :meth:`push` method:
275
275
276 - 'line': meant for line-oriented clients, inputs are appended one at a
276 - 'line': meant for line-oriented clients, inputs are appended one at a
277 time to the internal buffer and the whole buffer is compiled.
277 time to the internal buffer and the whole buffer is compiled.
278
278
279 - 'cell': meant for clients that can edit multi-line 'cells' of text at
279 - 'cell': meant for clients that can edit multi-line 'cells' of text at
280 a time. A cell can contain one or more blocks that can be compile in
280 a time. A cell can contain one or more blocks that can be compile in
281 'single' mode by Python. In this mode, each new input new input
281 'single' mode by Python. In this mode, each new input new input
282 completely replaces all prior inputs. Cell mode is thus equivalent
282 completely replaces all prior inputs. Cell mode is thus equivalent
283 to prepending a full reset() to every push() call.
283 to prepending a full reset() to every push() call.
284 """
284 """
285 self._buffer = []
285 self._buffer = []
286 self._compile = codeop.CommandCompiler()
286 self._compile = codeop.CommandCompiler()
287 self.encoding = get_input_encoding()
287 self.encoding = get_input_encoding()
288 self.input_mode = InputSplitter.input_mode if input_mode is None \
288 self.input_mode = InputSplitter.input_mode if input_mode is None \
289 else input_mode
289 else input_mode
290
290
291 def reset(self):
291 def reset(self):
292 """Reset the input buffer and associated state."""
292 """Reset the input buffer and associated state."""
293 self.indent_spaces = 0
293 self.indent_spaces = 0
294 self._buffer[:] = []
294 self._buffer[:] = []
295 self.source = ''
295 self.source = ''
296 self.code = None
296 self.code = None
297 self._is_complete = False
297 self._is_complete = False
298 self._full_dedent = False
298 self._full_dedent = False
299
299
300 def source_reset(self):
300 def source_reset(self):
301 """Return the input source and perform a full reset.
301 """Return the input source and perform a full reset.
302 """
302 """
303 out = self.source
303 out = self.source
304 self.reset()
304 self.reset()
305 return out
305 return out
306
306
307 def push(self, lines):
307 def push(self, lines):
308 """Push one or more lines of input.
308 """Push one or more lines of input.
309
309
310 This stores the given lines and returns a status code indicating
310 This stores the given lines and returns a status code indicating
311 whether the code forms a complete Python block or not.
311 whether the code forms a complete Python block or not.
312
312
313 Any exceptions generated in compilation are swallowed, but if an
313 Any exceptions generated in compilation are swallowed, but if an
314 exception was produced, the method returns True.
314 exception was produced, the method returns True.
315
315
316 Parameters
316 Parameters
317 ----------
317 ----------
318 lines : string
318 lines : string
319 One or more lines of Python input.
319 One or more lines of Python input.
320
320
321 Returns
321 Returns
322 -------
322 -------
323 is_complete : boolean
323 is_complete : boolean
324 True if the current input source (the result of the current input
324 True if the current input source (the result of the current input
325 plus prior inputs) forms a complete Python execution block. Note that
325 plus prior inputs) forms a complete Python execution block. Note that
326 this value is also stored as a private attribute (``_is_complete``), so it
326 this value is also stored as a private attribute (``_is_complete``), so it
327 can be queried at any time.
327 can be queried at any time.
328 """
328 """
329 if self.input_mode == 'cell':
329 if self.input_mode == 'cell':
330 self.reset()
330 self.reset()
331
331
332 self._store(lines)
332 self._store(lines)
333 source = self.source
333 source = self.source
334
334
335 # Before calling _compile(), reset the code object to None so that if an
335 # Before calling _compile(), reset the code object to None so that if an
336 # exception is raised in compilation, we don't mislead by having
336 # exception is raised in compilation, we don't mislead by having
337 # inconsistent code/source attributes.
337 # inconsistent code/source attributes.
338 self.code, self._is_complete = None, None
338 self.code, self._is_complete = None, None
339
339
340 # Honor termination lines properly
340 # Honor termination lines properly
341 if source.endswith('\\\n'):
341 if source.endswith('\\\n'):
342 return False
342 return False
343
343
344 self._update_indent(lines)
344 self._update_indent(lines)
345 try:
345 try:
346 self.code = self._compile(source, symbol="exec")
346 self.code = self._compile(source, symbol="exec")
347 # Invalid syntax can produce any of a number of different errors from
347 # Invalid syntax can produce any of a number of different errors from
348 # inside the compiler, so we have to catch them all. Syntax errors
348 # inside the compiler, so we have to catch them all. Syntax errors
349 # immediately produce a 'ready' block, so the invalid Python can be
349 # immediately produce a 'ready' block, so the invalid Python can be
350 # sent to the kernel for evaluation with possible ipython
350 # sent to the kernel for evaluation with possible ipython
351 # special-syntax conversion.
351 # special-syntax conversion.
352 except (SyntaxError, OverflowError, ValueError, TypeError,
352 except (SyntaxError, OverflowError, ValueError, TypeError,
353 MemoryError):
353 MemoryError):
354 self._is_complete = True
354 self._is_complete = True
355 else:
355 else:
356 # Compilation didn't produce any exceptions (though it may not have
356 # Compilation didn't produce any exceptions (though it may not have
357 # given a complete code object)
357 # given a complete code object)
358 self._is_complete = self.code is not None
358 self._is_complete = self.code is not None
359
359
360 return self._is_complete
360 return self._is_complete
361
361
362 def push_accepts_more(self):
362 def push_accepts_more(self):
363 """Return whether a block of interactive input can accept more input.
363 """Return whether a block of interactive input can accept more input.
364
364
365 This method is meant to be used by line-oriented frontends, who need to
365 This method is meant to be used by line-oriented frontends, who need to
366 guess whether a block is complete or not based solely on prior and
366 guess whether a block is complete or not based solely on prior and
367 current input lines. The InputSplitter considers it has a complete
367 current input lines. The InputSplitter considers it has a complete
368 interactive block and will not accept more input only when either a
368 interactive block and will not accept more input only when either a
369 SyntaxError is raised, or *all* of the following are true:
369 SyntaxError is raised, or *all* of the following are true:
370
370
371 1. The input compiles to a complete statement.
371 1. The input compiles to a complete statement.
372
372
373 2. The indentation level is flush-left (because if we are indented,
373 2. The indentation level is flush-left (because if we are indented,
374 like inside a function definition or for loop, we need to keep
374 like inside a function definition or for loop, we need to keep
375 reading new input).
375 reading new input).
376
376
377 3. There is one extra line consisting only of whitespace.
377 3. There is one extra line consisting only of whitespace.
378
378
379 Because of condition #3, this method should be used only by
379 Because of condition #3, this method should be used only by
380 *line-oriented* frontends, since it means that intermediate blank lines
380 *line-oriented* frontends, since it means that intermediate blank lines
381 are not allowed in function definitions (or any other indented block).
381 are not allowed in function definitions (or any other indented block).
382
382
383 If the current input produces a syntax error, this method immediately
383 If the current input produces a syntax error, this method immediately
384 returns False but does *not* raise the syntax error exception, as
384 returns False but does *not* raise the syntax error exception, as
385 typically clients will want to send invalid syntax to an execution
385 typically clients will want to send invalid syntax to an execution
386 backend which might convert the invalid syntax into valid Python via
386 backend which might convert the invalid syntax into valid Python via
387 one of the dynamic IPython mechanisms.
387 one of the dynamic IPython mechanisms.
388 """
388 """
389
389
390 # With incomplete input, unconditionally accept more
390 # With incomplete input, unconditionally accept more
391 if not self._is_complete:
391 if not self._is_complete:
392 return True
392 return True
393
393
394 # If we already have complete input and we're flush left, the answer
394 # If we already have complete input and we're flush left, the answer
395 # depends. In line mode, if there hasn't been any indentation,
395 # depends. In line mode, if there hasn't been any indentation,
396 # that's it. If we've come back from some indentation, we need
396 # that's it. If we've come back from some indentation, we need
397 # the blank final line to finish.
397 # the blank final line to finish.
398 # In cell mode, we need to check how many blocks the input so far
398 # In cell mode, we need to check how many blocks the input so far
399 # compiles into, because if there's already more than one full
399 # compiles into, because if there's already more than one full
400 # independent block of input, then the client has entered full
400 # independent block of input, then the client has entered full
401 # 'cell' mode and is feeding lines that each is complete. In this
401 # 'cell' mode and is feeding lines that each is complete. In this
402 # case we should then keep accepting. The Qt terminal-like console
402 # case we should then keep accepting. The Qt terminal-like console
403 # does precisely this, to provide the convenience of terminal-like
403 # does precisely this, to provide the convenience of terminal-like
404 # input of single expressions, but allowing the user (with a
404 # input of single expressions, but allowing the user (with a
405 # separate keystroke) to switch to 'cell' mode and type multiple
405 # separate keystroke) to switch to 'cell' mode and type multiple
406 # expressions in one shot.
406 # expressions in one shot.
407 if self.indent_spaces==0:
407 if self.indent_spaces==0:
408 if self.input_mode=='line':
408 if self.input_mode=='line':
409 if not self._full_dedent:
409 if not self._full_dedent:
410 return False
410 return False
411 else:
411 else:
412 try:
412 try:
413 code_ast = ast.parse(u''.join(self._buffer))
413 code_ast = ast.parse(u''.join(self._buffer))
414 except Exception:
414 except Exception:
415 return False
415 return False
416 else:
416 else:
417 if len(code_ast.body) == 1:
417 if len(code_ast.body) == 1:
418 return False
418 return False
419
419
420 # When input is complete, then termination is marked by an extra blank
420 # When input is complete, then termination is marked by an extra blank
421 # line at the end.
421 # line at the end.
422 last_line = self.source.splitlines()[-1]
422 last_line = self.source.splitlines()[-1]
423 return bool(last_line and not last_line.isspace())
423 return bool(last_line and not last_line.isspace())
424
424
425 #------------------------------------------------------------------------
425 #------------------------------------------------------------------------
426 # Private interface
426 # Private interface
427 #------------------------------------------------------------------------
427 #------------------------------------------------------------------------
428
428
429 def _find_indent(self, line):
429 def _find_indent(self, line):
430 """Compute the new indentation level for a single line.
430 """Compute the new indentation level for a single line.
431
431
432 Parameters
432 Parameters
433 ----------
433 ----------
434 line : str
434 line : str
435 A single new line of non-whitespace, non-comment Python input.
435 A single new line of non-whitespace, non-comment Python input.
436
436
437 Returns
437 Returns
438 -------
438 -------
439 indent_spaces : int
439 indent_spaces : int
440 New value for the indent level (it may be equal to self.indent_spaces
440 New value for the indent level (it may be equal to self.indent_spaces
441 if indentation doesn't change.
441 if indentation doesn't change.
442
442
443 full_dedent : boolean
443 full_dedent : boolean
444 Whether the new line causes a full flush-left dedent.
444 Whether the new line causes a full flush-left dedent.
445 """
445 """
446 indent_spaces = self.indent_spaces
446 indent_spaces = self.indent_spaces
447 full_dedent = self._full_dedent
447 full_dedent = self._full_dedent
448
448
449 inisp = num_ini_spaces(line)
449 inisp = num_ini_spaces(line)
450 if inisp < indent_spaces:
450 if inisp < indent_spaces:
451 indent_spaces = inisp
451 indent_spaces = inisp
452 if indent_spaces <= 0:
452 if indent_spaces <= 0:
453 #print 'Full dedent in text',self.source # dbg
453 #print 'Full dedent in text',self.source # dbg
454 full_dedent = True
454 full_dedent = True
455
455
456 if line.rstrip()[-1] == ':':
456 if line.rstrip()[-1] == ':':
457 indent_spaces += 4
457 indent_spaces += 4
458 elif dedent_re.match(line):
458 elif dedent_re.match(line):
459 indent_spaces -= 4
459 indent_spaces -= 4
460 if indent_spaces <= 0:
460 if indent_spaces <= 0:
461 full_dedent = True
461 full_dedent = True
462
462
463 # Safety
463 # Safety
464 if indent_spaces < 0:
464 if indent_spaces < 0:
465 indent_spaces = 0
465 indent_spaces = 0
466 #print 'safety' # dbg
466 #print 'safety' # dbg
467
467
468 return indent_spaces, full_dedent
468 return indent_spaces, full_dedent
469
469
470 def _update_indent(self, lines):
470 def _update_indent(self, lines):
471 for line in remove_comments(lines).splitlines():
471 for line in remove_comments(lines).splitlines():
472 if line and not line.isspace():
472 if line and not line.isspace():
473 self.indent_spaces, self._full_dedent = self._find_indent(line)
473 self.indent_spaces, self._full_dedent = self._find_indent(line)
474
474
475 def _store(self, lines, buffer=None, store='source'):
475 def _store(self, lines, buffer=None, store='source'):
476 """Store one or more lines of input.
476 """Store one or more lines of input.
477
477
478 If input lines are not newline-terminated, a newline is automatically
478 If input lines are not newline-terminated, a newline is automatically
479 appended."""
479 appended."""
480
480
481 if buffer is None:
481 if buffer is None:
482 buffer = self._buffer
482 buffer = self._buffer
483
483
484 if lines.endswith('\n'):
484 if lines.endswith('\n'):
485 buffer.append(lines)
485 buffer.append(lines)
486 else:
486 else:
487 buffer.append(lines+'\n')
487 buffer.append(lines+'\n')
488 setattr(self, store, self._set_source(buffer))
488 setattr(self, store, self._set_source(buffer))
489
489
490 def _set_source(self, buffer):
490 def _set_source(self, buffer):
491 return u''.join(buffer)
491 return u''.join(buffer)
492
492
493
493
494 class IPythonInputSplitter(InputSplitter):
494 class IPythonInputSplitter(InputSplitter):
495 """An input splitter that recognizes all of IPython's special syntax."""
495 """An input splitter that recognizes all of IPython's special syntax."""
496
496
497 # String with raw, untransformed input.
497 # String with raw, untransformed input.
498 source_raw = ''
498 source_raw = ''
499
499
500 # Flag to track when a transformer has stored input that it hasn't given
500 # Flag to track when a transformer has stored input that it hasn't given
501 # back yet.
501 # back yet.
502 transformer_accumulating = False
502 transformer_accumulating = False
503
503
504 # Flag to track when assemble_python_lines has stored input that it hasn't
504 # Flag to track when assemble_python_lines has stored input that it hasn't
505 # given back yet.
505 # given back yet.
506 within_python_line = False
506 within_python_line = False
507
507
508 # Private attributes
508 # Private attributes
509
509
510 # List with lines of raw input accumulated so far.
510 # List with lines of raw input accumulated so far.
511 _buffer_raw = None
511 _buffer_raw = None
512
512
513 def __init__(self, input_mode=None, physical_line_transforms=None,
513 def __init__(self, input_mode=None, physical_line_transforms=None,
514 logical_line_transforms=None, python_line_transforms=None):
514 logical_line_transforms=None, python_line_transforms=None):
515 super(IPythonInputSplitter, self).__init__(input_mode)
515 super(IPythonInputSplitter, self).__init__(input_mode)
516 self._buffer_raw = []
516 self._buffer_raw = []
517 self._validate = True
517 self._validate = True
518
518
519 self.physical_line_transforms = physical_line_transforms or \
519 self.physical_line_transforms = physical_line_transforms or \
520 [leading_indent(),
520 [leading_indent(),
521 classic_prompt(),
521 classic_prompt(),
522 ipy_prompt(),
522 ipy_prompt(),
523 ]
523 ]
524
524
525 self.assemble_logical_lines = assemble_logical_lines()
525 self.assemble_logical_lines = assemble_logical_lines()
526 self.logical_line_transforms = logical_line_transforms or \
526 self.logical_line_transforms = logical_line_transforms or \
527 [cellmagic(),
527 [cellmagic(),
528 help_end(),
528 help_end(),
529 escaped_commands(),
529 escaped_commands(),
530 assign_from_magic(),
530 assign_from_magic(),
531 assign_from_system(),
531 assign_from_system(),
532 ]
532 ]
533
533
534 self.assemble_python_lines = assemble_python_lines()
534 self.assemble_python_lines = assemble_python_lines()
535 self.python_line_transforms = python_line_transforms or []
535 self.python_line_transforms = python_line_transforms or []
536
536
537 @property
537 @property
538 def transforms(self):
538 def transforms(self):
539 "Quick access to all transformers."
539 "Quick access to all transformers."
540 return self.physical_line_transforms + \
540 return self.physical_line_transforms + \
541 [self.assemble_logical_lines] + self.logical_line_transforms + \
541 [self.assemble_logical_lines] + self.logical_line_transforms + \
542 [self.assemble_python_lines] + self.python_line_transforms
542 [self.assemble_python_lines] + self.python_line_transforms
543
543
544 @property
544 @property
545 def transforms_in_use(self):
545 def transforms_in_use(self):
546 """Transformers, excluding logical line transformers if we're in a
546 """Transformers, excluding logical line transformers if we're in a
547 Python line."""
547 Python line."""
548 t = self.physical_line_transforms + [self.assemble_logical_lines]
548 t = self.physical_line_transforms[:]
549 if not self.within_python_line:
549 if not self.within_python_line:
550 t += self.logical_line_transforms
550 t += [self.assemble_logical_lines] + self.logical_line_transforms
551 return t + [self.assemble_python_lines] + self.python_line_transforms
551 return t + [self.assemble_python_lines] + self.python_line_transforms
552
552
553 def reset(self):
553 def reset(self):
554 """Reset the input buffer and associated state."""
554 """Reset the input buffer and associated state."""
555 super(IPythonInputSplitter, self).reset()
555 super(IPythonInputSplitter, self).reset()
556 self._buffer_raw[:] = []
556 self._buffer_raw[:] = []
557 self.source_raw = ''
557 self.source_raw = ''
558 self.transformer_accumulating = False
558 self.transformer_accumulating = False
559 self.within_python_line = False
559 for t in self.transforms:
560 for t in self.transforms:
560 t.reset()
561 t.reset()
561
562
562 def flush_transformers(self):
563 def flush_transformers(self):
563 def _flush(transform, out):
564 def _flush(transform, out):
564 if out is not None:
565 if out is not None:
565 tmp = transform.push(out)
566 tmp = transform.push(out)
566 return tmp or transform.reset() or None
567 return tmp or transform.reset() or None
567 else:
568 else:
568 return transform.reset() or None
569 return transform.reset() or None
569
570
570 out = None
571 out = None
571 for t in self.transforms_in_use:
572 for t in self.transforms_in_use:
572 out = _flush(t, out)
573 out = _flush(t, out)
573
574
574 if out is not None:
575 if out is not None:
575 self._store(out)
576 self._store(out)
576
577
577 def source_raw_reset(self):
578 def source_raw_reset(self):
578 """Return input and raw source and perform a full reset.
579 """Return input and raw source and perform a full reset.
579 """
580 """
580 self.flush_transformers()
581 self.flush_transformers()
581 out = self.source
582 out = self.source
582 out_r = self.source_raw
583 out_r = self.source_raw
583 self.reset()
584 self.reset()
584 return out, out_r
585 return out, out_r
585
586
586 def source_reset(self):
587 def source_reset(self):
587 self.flush_transformers()
588 self.flush_transformers()
588 return super(IPythonInputSplitter, self).source_reset()
589 return super(IPythonInputSplitter, self).source_reset()
589
590
590 def push_accepts_more(self):
591 def push_accepts_more(self):
591 if self.transformer_accumulating:
592 if self.transformer_accumulating:
592 return True
593 return True
593 else:
594 else:
594 return super(IPythonInputSplitter, self).push_accepts_more()
595 return super(IPythonInputSplitter, self).push_accepts_more()
595
596
596 def transform_cell(self, cell):
597 def transform_cell(self, cell):
597 """Process and translate a cell of input.
598 """Process and translate a cell of input.
598 """
599 """
599 self.reset()
600 self.reset()
600 self.push(cell)
601 self.push(cell)
601 return self.source_reset()
602 return self.source_reset()
602
603
603 def push(self, lines):
604 def push(self, lines):
604 """Push one or more lines of IPython input.
605 """Push one or more lines of IPython input.
605
606
606 This stores the given lines and returns a status code indicating
607 This stores the given lines and returns a status code indicating
607 whether the code forms a complete Python block or not, after processing
608 whether the code forms a complete Python block or not, after processing
608 all input lines for special IPython syntax.
609 all input lines for special IPython syntax.
609
610
610 Any exceptions generated in compilation are swallowed, but if an
611 Any exceptions generated in compilation are swallowed, but if an
611 exception was produced, the method returns True.
612 exception was produced, the method returns True.
612
613
613 Parameters
614 Parameters
614 ----------
615 ----------
615 lines : string
616 lines : string
616 One or more lines of Python input.
617 One or more lines of Python input.
617
618
618 Returns
619 Returns
619 -------
620 -------
620 is_complete : boolean
621 is_complete : boolean
621 True if the current input source (the result of the current input
622 True if the current input source (the result of the current input
622 plus prior inputs) forms a complete Python execution block. Note that
623 plus prior inputs) forms a complete Python execution block. Note that
623 this value is also stored as a private attribute (_is_complete), so it
624 this value is also stored as a private attribute (_is_complete), so it
624 can be queried at any time.
625 can be queried at any time.
625 """
626 """
626
627
627 # We must ensure all input is pure unicode
628 # We must ensure all input is pure unicode
628 lines = cast_unicode(lines, self.encoding)
629 lines = cast_unicode(lines, self.encoding)
629
630
630 # ''.splitlines() --> [], but we need to push the empty line to transformers
631 # ''.splitlines() --> [], but we need to push the empty line to transformers
631 lines_list = lines.splitlines()
632 lines_list = lines.splitlines()
632 if not lines_list:
633 if not lines_list:
633 lines_list = ['']
634 lines_list = ['']
634
635
635 # Transform logic
636 # Transform logic
636 #
637 #
637 # We only apply the line transformers to the input if we have either no
638 # We only apply the line transformers to the input if we have either no
638 # input yet, or complete input, or if the last line of the buffer ends
639 # input yet, or complete input, or if the last line of the buffer ends
639 # with ':' (opening an indented block). This prevents the accidental
640 # with ':' (opening an indented block). This prevents the accidental
640 # transformation of escapes inside multiline expressions like
641 # transformation of escapes inside multiline expressions like
641 # triple-quoted strings or parenthesized expressions.
642 # triple-quoted strings or parenthesized expressions.
642 #
643 #
643 # The last heuristic, while ugly, ensures that the first line of an
644 # The last heuristic, while ugly, ensures that the first line of an
644 # indented block is correctly transformed.
645 # indented block is correctly transformed.
645 #
646 #
646 # FIXME: try to find a cleaner approach for this last bit.
647 # FIXME: try to find a cleaner approach for this last bit.
647
648
648 # If we were in 'block' mode, since we're going to pump the parent
649 # If we were in 'block' mode, since we're going to pump the parent
649 # class by hand line by line, we need to temporarily switch out to
650 # class by hand line by line, we need to temporarily switch out to
650 # 'line' mode, do a single manual reset and then feed the lines one
651 # 'line' mode, do a single manual reset and then feed the lines one
651 # by one. Note that this only matters if the input has more than one
652 # by one. Note that this only matters if the input has more than one
652 # line.
653 # line.
653 changed_input_mode = False
654 changed_input_mode = False
654
655
655 if self.input_mode == 'cell':
656 if self.input_mode == 'cell':
656 self.reset()
657 self.reset()
657 changed_input_mode = True
658 changed_input_mode = True
658 saved_input_mode = 'cell'
659 saved_input_mode = 'cell'
659 self.input_mode = 'line'
660 self.input_mode = 'line'
660
661
661 # Store raw source before applying any transformations to it. Note
662 # Store raw source before applying any transformations to it. Note
662 # that this must be done *after* the reset() call that would otherwise
663 # that this must be done *after* the reset() call that would otherwise
663 # flush the buffer.
664 # flush the buffer.
664 self._store(lines, self._buffer_raw, 'source_raw')
665 self._store(lines, self._buffer_raw, 'source_raw')
665
666
666 try:
667 try:
667 for line in lines_list:
668 for line in lines_list:
668 out = self.push_line(line)
669 out = self.push_line(line)
669 finally:
670 finally:
670 if changed_input_mode:
671 if changed_input_mode:
671 self.input_mode = saved_input_mode
672 self.input_mode = saved_input_mode
672
673
673 return out
674 return out
674
675
675 def push_line(self, line):
676 def push_line(self, line):
676 buf = self._buffer
677 buf = self._buffer
677
678
678 def _accumulating(dbg):
679 def _accumulating(dbg):
679 #print(dbg)
680 #print(dbg)
680 self.transformer_accumulating = True
681 self.transformer_accumulating = True
681 return False
682 return False
682
683
683 for transformer in self.physical_line_transforms:
684 for transformer in self.physical_line_transforms:
684 line = transformer.push(line)
685 line = transformer.push(line)
685 if line is None:
686 if line is None:
686 return _accumulating(transformer)
687 return _accumulating(transformer)
687
688
688 line = self.assemble_logical_lines.push(line)
689 if line is None:
690 return _accumulating('acc logical line')
691
692 if not self.within_python_line:
689 if not self.within_python_line:
690 line = self.assemble_logical_lines.push(line)
691 if line is None:
692 return _accumulating('acc logical line')
693
693 for transformer in self.logical_line_transforms:
694 for transformer in self.logical_line_transforms:
694 line = transformer.push(line)
695 line = transformer.push(line)
695 if line is None:
696 if line is None:
696 return _accumulating(transformer)
697 return _accumulating(transformer)
697
698
698 line = self.assemble_python_lines.push(line)
699 line = self.assemble_python_lines.push(line)
699 if line is None:
700 if line is None:
700 self.within_python_line = True
701 self.within_python_line = True
701 return _accumulating('acc python line')
702 return _accumulating('acc python line')
702 else:
703 else:
703 self.within_python_line = False
704 self.within_python_line = False
704
705
705 for transformer in self.python_line_transforms:
706 for transformer in self.python_line_transforms:
706 line = transformer.push(line)
707 line = transformer.push(line)
707 if line is None:
708 if line is None:
708 return _accumulating(transformer)
709 return _accumulating(transformer)
709
710
710 #print("transformers clear") #debug
711 #print("transformers clear") #debug
711 self.transformer_accumulating = False
712 self.transformer_accumulating = False
712 return super(IPythonInputSplitter, self).push(line)
713 return super(IPythonInputSplitter, self).push(line)
@@ -1,434 +1,436 b''
1 import abc
1 import abc
2 import functools
2 import functools
3 import re
3 import re
4 from StringIO import StringIO
4 from StringIO import StringIO
5
5
6 from IPython.core.splitinput import split_user_input, LineInfo
6 from IPython.core.splitinput import split_user_input, LineInfo
7 from IPython.utils import tokenize2
7 from IPython.utils import tokenize2
8 from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError
8 from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Globals
11 # Globals
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # The escape sequences that define the syntax transformations IPython will
14 # The escape sequences that define the syntax transformations IPython will
15 # apply to user input. These can NOT be just changed here: many regular
15 # apply to user input. These can NOT be just changed here: many regular
16 # expressions and other parts of the code may use their hardcoded values, and
16 # expressions and other parts of the code may use their hardcoded values, and
17 # for all intents and purposes they constitute the 'IPython syntax', so they
17 # for all intents and purposes they constitute the 'IPython syntax', so they
18 # should be considered fixed.
18 # should be considered fixed.
19
19
20 ESC_SHELL = '!' # Send line to underlying system shell
20 ESC_SHELL = '!' # Send line to underlying system shell
21 ESC_SH_CAP = '!!' # Send line to system shell and capture output
21 ESC_SH_CAP = '!!' # Send line to system shell and capture output
22 ESC_HELP = '?' # Find information about object
22 ESC_HELP = '?' # Find information about object
23 ESC_HELP2 = '??' # Find extra-detailed information about object
23 ESC_HELP2 = '??' # Find extra-detailed information about object
24 ESC_MAGIC = '%' # Call magic function
24 ESC_MAGIC = '%' # Call magic function
25 ESC_MAGIC2 = '%%' # Call cell-magic function
25 ESC_MAGIC2 = '%%' # Call cell-magic function
26 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
26 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
27 ESC_QUOTE2 = ';' # Quote all args as a single string, call
27 ESC_QUOTE2 = ';' # Quote all args as a single string, call
28 ESC_PAREN = '/' # Call first argument with rest of line as arguments
28 ESC_PAREN = '/' # Call first argument with rest of line as arguments
29
29
30 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
30 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
31 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
31 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
32 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
32 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
33
33
34
34
35 class InputTransformer(object):
35 class InputTransformer(object):
36 """Abstract base class for line-based input transformers."""
36 """Abstract base class for line-based input transformers."""
37 __metaclass__ = abc.ABCMeta
37 __metaclass__ = abc.ABCMeta
38
38
39 @abc.abstractmethod
39 @abc.abstractmethod
40 def push(self, line):
40 def push(self, line):
41 """Send a line of input to the transformer, returning the transformed
41 """Send a line of input to the transformer, returning the transformed
42 input or None if the transformer is waiting for more input.
42 input or None if the transformer is waiting for more input.
43
43
44 Must be overridden by subclasses.
44 Must be overridden by subclasses.
45 """
45 """
46 pass
46 pass
47
47
48 @abc.abstractmethod
48 @abc.abstractmethod
49 def reset(self):
49 def reset(self):
50 """Return, transformed any lines that the transformer has accumulated,
50 """Return, transformed any lines that the transformer has accumulated,
51 and reset its internal state.
51 and reset its internal state.
52
52
53 Must be overridden by subclasses.
53 Must be overridden by subclasses.
54 """
54 """
55 pass
55 pass
56
56
57 @classmethod
57 @classmethod
58 def wrap(cls, func):
58 def wrap(cls, func):
59 """Can be used by subclasses as a decorator, to return a factory that
59 """Can be used by subclasses as a decorator, to return a factory that
60 will allow instantiation with the decorated object.
60 will allow instantiation with the decorated object.
61 """
61 """
62 @functools.wraps(func)
62 @functools.wraps(func)
63 def transformer_factory():
63 def transformer_factory():
64 return cls(func)
64 return cls(func)
65
65
66 return transformer_factory
66 return transformer_factory
67
67
68 class StatelessInputTransformer(InputTransformer):
68 class StatelessInputTransformer(InputTransformer):
69 """Wrapper for a stateless input transformer implemented as a function."""
69 """Wrapper for a stateless input transformer implemented as a function."""
70 def __init__(self, func):
70 def __init__(self, func):
71 self.func = func
71 self.func = func
72
72
73 def __repr__(self):
73 def __repr__(self):
74 return "StatelessInputTransformer(func={!r})".format(self.func)
74 return "StatelessInputTransformer(func={!r})".format(self.func)
75
75
76 def push(self, line):
76 def push(self, line):
77 """Send a line of input to the transformer, returning the
77 """Send a line of input to the transformer, returning the
78 transformed input."""
78 transformed input."""
79 return self.func(line)
79 return self.func(line)
80
80
81 def reset(self):
81 def reset(self):
82 """No-op - exists for compatibility."""
82 """No-op - exists for compatibility."""
83 pass
83 pass
84
84
85 class CoroutineInputTransformer(InputTransformer):
85 class CoroutineInputTransformer(InputTransformer):
86 """Wrapper for an input transformer implemented as a coroutine."""
86 """Wrapper for an input transformer implemented as a coroutine."""
87 def __init__(self, coro):
87 def __init__(self, coro):
88 # Prime it
88 # Prime it
89 self.coro = coro()
89 self.coro = coro()
90 next(self.coro)
90 next(self.coro)
91
91
92 def __repr__(self):
92 def __repr__(self):
93 return "CoroutineInputTransformer(coro={!r})".format(self.coro)
93 return "CoroutineInputTransformer(coro={!r})".format(self.coro)
94
94
95 def push(self, line):
95 def push(self, line):
96 """Send a line of input to the transformer, returning the
96 """Send a line of input to the transformer, returning the
97 transformed input or None if the transformer is waiting for more
97 transformed input or None if the transformer is waiting for more
98 input.
98 input.
99 """
99 """
100 return self.coro.send(line)
100 return self.coro.send(line)
101
101
102 def reset(self):
102 def reset(self):
103 """Return, transformed any lines that the transformer has
103 """Return, transformed any lines that the transformer has
104 accumulated, and reset its internal state.
104 accumulated, and reset its internal state.
105 """
105 """
106 return self.coro.send(None)
106 return self.coro.send(None)
107
107
108 class TokenInputTransformer(InputTransformer):
108 class TokenInputTransformer(InputTransformer):
109 """Wrapper for a token-based input transformer.
109 """Wrapper for a token-based input transformer.
110
110
111 func should accept a list of tokens (5-tuples, see tokenize docs), and
111 func should accept a list of tokens (5-tuples, see tokenize docs), and
112 return an iterable which can be passed to tokenize.untokenize().
112 return an iterable which can be passed to tokenize.untokenize().
113 """
113 """
114 def __init__(self, func):
114 def __init__(self, func):
115 self.func = func
115 self.func = func
116 self.current_line = ""
116 self.current_line = ""
117 self.line_used = False
117 self.line_used = False
118 self.reset_tokenizer()
118 self.reset_tokenizer()
119
119
120 def reset_tokenizer(self):
120 def reset_tokenizer(self):
121 self.tokenizer = generate_tokens(self.get_line)
121 self.tokenizer = generate_tokens(self.get_line)
122
122
123 def get_line(self):
123 def get_line(self):
124 if self.line_used:
124 if self.line_used:
125 raise TokenError
125 raise TokenError
126 self.line_used = True
126 self.line_used = True
127 return self.current_line
127 return self.current_line
128
128
129 def push(self, line):
129 def push(self, line):
130 self.current_line += line + "\n"
130 self.current_line += line + "\n"
131 if self.current_line.isspace():
131 if self.current_line.isspace():
132 return self.reset()
132 return self.reset()
133
133
134 self.line_used = False
134 self.line_used = False
135 tokens = []
135 tokens = []
136 stop_at_NL = False
136 stop_at_NL = False
137 try:
137 try:
138 for intok in self.tokenizer:
138 for intok in self.tokenizer:
139 tokens.append(intok)
139 tokens.append(intok)
140 t = intok[0]
140 t = intok[0]
141 if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL):
141 if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL):
142 # Stop before we try to pull a line we don't have yet
142 # Stop before we try to pull a line we don't have yet
143 break
143 break
144 elif t == tokenize2.ERRORTOKEN:
144 elif t == tokenize2.ERRORTOKEN:
145 stop_at_NL = True
145 stop_at_NL = True
146 except TokenError:
146 except TokenError:
147 # Multi-line statement - stop and try again with the next line
147 # Multi-line statement - stop and try again with the next line
148 self.reset_tokenizer()
148 self.reset_tokenizer()
149 return None
149 return None
150
150
151 return self.output(tokens)
151 return self.output(tokens)
152
152
153 def output(self, tokens):
153 def output(self, tokens):
154 self.current_line = ""
154 self.current_line = ""
155 self.reset_tokenizer()
155 self.reset_tokenizer()
156 return untokenize(self.func(tokens)).rstrip('\n')
156 return untokenize(self.func(tokens)).rstrip('\n')
157
157
158 def reset(self):
158 def reset(self):
159 l = self.current_line
159 l = self.current_line
160 self.current_line = ""
160 self.current_line = ""
161 self.reset_tokenizer()
161 self.reset_tokenizer()
162 if l:
162 if l:
163 return l.rstrip('\n')
163 return l.rstrip('\n')
164
164
165 class assemble_python_lines(TokenInputTransformer):
165 class assemble_python_lines(TokenInputTransformer):
166 def __init__(self):
166 def __init__(self):
167 super(assemble_python_lines, self).__init__(None)
167 super(assemble_python_lines, self).__init__(None)
168
168
169 def output(self, tokens):
169 def output(self, tokens):
170 return self.reset()
170 return self.reset()
171
171
172 @CoroutineInputTransformer.wrap
172 @CoroutineInputTransformer.wrap
173 def assemble_logical_lines():
173 def assemble_logical_lines():
174 """Join lines following explicit line continuations (\)"""
174 """Join lines following explicit line continuations (\)"""
175 line = ''
175 line = ''
176 while True:
176 while True:
177 line = (yield line)
177 line = (yield line)
178 if not line or line.isspace():
178 if not line or line.isspace():
179 continue
179 continue
180
180
181 parts = []
181 parts = []
182 while line is not None:
182 while line is not None:
183 parts.append(line.rstrip('\\'))
183 if line.endswith('\\') and (not has_comment(line)):
184 if not line.endswith('\\'):
184 parts.append(line[:-1])
185 line = (yield None) # Get another line
186 else:
187 parts.append(line)
185 break
188 break
186 line = (yield None)
187
189
188 # Output
190 # Output
189 line = ' '.join(parts)
191 line = ''.join(parts)
190
192
191 # Utilities
193 # Utilities
192 def _make_help_call(target, esc, lspace, next_input=None):
194 def _make_help_call(target, esc, lspace, next_input=None):
193 """Prepares a pinfo(2)/psearch call from a target name and the escape
195 """Prepares a pinfo(2)/psearch call from a target name and the escape
194 (i.e. ? or ??)"""
196 (i.e. ? or ??)"""
195 method = 'pinfo2' if esc == '??' \
197 method = 'pinfo2' if esc == '??' \
196 else 'psearch' if '*' in target \
198 else 'psearch' if '*' in target \
197 else 'pinfo'
199 else 'pinfo'
198 arg = " ".join([method, target])
200 arg = " ".join([method, target])
199 if next_input is None:
201 if next_input is None:
200 return '%sget_ipython().magic(%r)' % (lspace, arg)
202 return '%sget_ipython().magic(%r)' % (lspace, arg)
201 else:
203 else:
202 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
204 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
203 (lspace, next_input, arg)
205 (lspace, next_input, arg)
204
206
205 # These define the transformations for the different escape characters.
207 # These define the transformations for the different escape characters.
206 def _tr_system(line_info):
208 def _tr_system(line_info):
207 "Translate lines escaped with: !"
209 "Translate lines escaped with: !"
208 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
210 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
209 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
211 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
210
212
211 def _tr_system2(line_info):
213 def _tr_system2(line_info):
212 "Translate lines escaped with: !!"
214 "Translate lines escaped with: !!"
213 cmd = line_info.line.lstrip()[2:]
215 cmd = line_info.line.lstrip()[2:]
214 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
216 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
215
217
216 def _tr_help(line_info):
218 def _tr_help(line_info):
217 "Translate lines escaped with: ?/??"
219 "Translate lines escaped with: ?/??"
218 # A naked help line should just fire the intro help screen
220 # A naked help line should just fire the intro help screen
219 if not line_info.line[1:]:
221 if not line_info.line[1:]:
220 return 'get_ipython().show_usage()'
222 return 'get_ipython().show_usage()'
221
223
222 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
224 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
223
225
224 def _tr_magic(line_info):
226 def _tr_magic(line_info):
225 "Translate lines escaped with: %"
227 "Translate lines escaped with: %"
226 tpl = '%sget_ipython().magic(%r)'
228 tpl = '%sget_ipython().magic(%r)'
227 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
229 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
228 return tpl % (line_info.pre, cmd)
230 return tpl % (line_info.pre, cmd)
229
231
230 def _tr_quote(line_info):
232 def _tr_quote(line_info):
231 "Translate lines escaped with: ,"
233 "Translate lines escaped with: ,"
232 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
234 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
233 '", "'.join(line_info.the_rest.split()) )
235 '", "'.join(line_info.the_rest.split()) )
234
236
235 def _tr_quote2(line_info):
237 def _tr_quote2(line_info):
236 "Translate lines escaped with: ;"
238 "Translate lines escaped with: ;"
237 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
239 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
238 line_info.the_rest)
240 line_info.the_rest)
239
241
240 def _tr_paren(line_info):
242 def _tr_paren(line_info):
241 "Translate lines escaped with: /"
243 "Translate lines escaped with: /"
242 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
244 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
243 ", ".join(line_info.the_rest.split()))
245 ", ".join(line_info.the_rest.split()))
244
246
245 tr = { ESC_SHELL : _tr_system,
247 tr = { ESC_SHELL : _tr_system,
246 ESC_SH_CAP : _tr_system2,
248 ESC_SH_CAP : _tr_system2,
247 ESC_HELP : _tr_help,
249 ESC_HELP : _tr_help,
248 ESC_HELP2 : _tr_help,
250 ESC_HELP2 : _tr_help,
249 ESC_MAGIC : _tr_magic,
251 ESC_MAGIC : _tr_magic,
250 ESC_QUOTE : _tr_quote,
252 ESC_QUOTE : _tr_quote,
251 ESC_QUOTE2 : _tr_quote2,
253 ESC_QUOTE2 : _tr_quote2,
252 ESC_PAREN : _tr_paren }
254 ESC_PAREN : _tr_paren }
253
255
254 @StatelessInputTransformer.wrap
256 @StatelessInputTransformer.wrap
255 def escaped_commands(line):
257 def escaped_commands(line):
256 """Transform escaped commands - %magic, !system, ?help + various autocalls.
258 """Transform escaped commands - %magic, !system, ?help + various autocalls.
257 """
259 """
258 if not line or line.isspace():
260 if not line or line.isspace():
259 return line
261 return line
260 lineinf = LineInfo(line)
262 lineinf = LineInfo(line)
261 if lineinf.esc not in tr:
263 if lineinf.esc not in tr:
262 return line
264 return line
263
265
264 return tr[lineinf.esc](lineinf)
266 return tr[lineinf.esc](lineinf)
265
267
266 _initial_space_re = re.compile(r'\s*')
268 _initial_space_re = re.compile(r'\s*')
267
269
268 _help_end_re = re.compile(r"""(%{0,2}
270 _help_end_re = re.compile(r"""(%{0,2}
269 [a-zA-Z_*][\w*]* # Variable name
271 [a-zA-Z_*][\w*]* # Variable name
270 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
272 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
271 )
273 )
272 (\?\??)$ # ? or ??""",
274 (\?\??)$ # ? or ??""",
273 re.VERBOSE)
275 re.VERBOSE)
274
276
275 def has_comment(src):
277 def has_comment(src):
276 """Indicate whether an input line has (i.e. ends in, or is) a comment.
278 """Indicate whether an input line has (i.e. ends in, or is) a comment.
277
279
278 This uses tokenize, so it can distinguish comments from # inside strings.
280 This uses tokenize, so it can distinguish comments from # inside strings.
279
281
280 Parameters
282 Parameters
281 ----------
283 ----------
282 src : string
284 src : string
283 A single line input string.
285 A single line input string.
284
286
285 Returns
287 Returns
286 -------
288 -------
287 comment : bool
289 comment : bool
288 True if source has a comment.
290 True if source has a comment.
289 """
291 """
290 readline = StringIO(src).readline
292 readline = StringIO(src).readline
291 toktypes = set()
293 toktypes = set()
292 try:
294 try:
293 for t in generate_tokens(readline):
295 for t in generate_tokens(readline):
294 toktypes.add(t[0])
296 toktypes.add(t[0])
295 except TokenError:
297 except TokenError:
296 pass
298 pass
297 return(tokenize2.COMMENT in toktypes)
299 return(tokenize2.COMMENT in toktypes)
298
300
299
301
300 @StatelessInputTransformer.wrap
302 @StatelessInputTransformer.wrap
301 def help_end(line):
303 def help_end(line):
302 """Translate lines with ?/?? at the end"""
304 """Translate lines with ?/?? at the end"""
303 m = _help_end_re.search(line)
305 m = _help_end_re.search(line)
304 if m is None or has_comment(line):
306 if m is None or has_comment(line):
305 return line
307 return line
306 target = m.group(1)
308 target = m.group(1)
307 esc = m.group(3)
309 esc = m.group(3)
308 lspace = _initial_space_re.match(line).group(0)
310 lspace = _initial_space_re.match(line).group(0)
309
311
310 # If we're mid-command, put it back on the next prompt for the user.
312 # If we're mid-command, put it back on the next prompt for the user.
311 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
313 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
312
314
313 return _make_help_call(target, esc, lspace, next_input)
315 return _make_help_call(target, esc, lspace, next_input)
314
316
315
317
316 @CoroutineInputTransformer.wrap
318 @CoroutineInputTransformer.wrap
317 def cellmagic():
319 def cellmagic():
318 """Captures & transforms cell magics.
320 """Captures & transforms cell magics.
319
321
320 After a cell magic is started, this stores up any lines it gets until it is
322 After a cell magic is started, this stores up any lines it gets until it is
321 reset (sent None).
323 reset (sent None).
322 """
324 """
323 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
325 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
324 cellmagic_help_re = re.compile('%%\w+\?')
326 cellmagic_help_re = re.compile('%%\w+\?')
325 line = ''
327 line = ''
326 while True:
328 while True:
327 line = (yield line)
329 line = (yield line)
328 if (not line) or (not line.startswith(ESC_MAGIC2)):
330 if (not line) or (not line.startswith(ESC_MAGIC2)):
329 continue
331 continue
330
332
331 if cellmagic_help_re.match(line):
333 if cellmagic_help_re.match(line):
332 # This case will be handled by help_end
334 # This case will be handled by help_end
333 continue
335 continue
334
336
335 first = line
337 first = line
336 body = []
338 body = []
337 line = (yield None)
339 line = (yield None)
338 while (line is not None) and (line.strip() != ''):
340 while (line is not None) and (line.strip() != ''):
339 body.append(line)
341 body.append(line)
340 line = (yield None)
342 line = (yield None)
341
343
342 # Output
344 # Output
343 magic_name, _, first = first.partition(' ')
345 magic_name, _, first = first.partition(' ')
344 magic_name = magic_name.lstrip(ESC_MAGIC2)
346 magic_name = magic_name.lstrip(ESC_MAGIC2)
345 line = tpl % (magic_name, first, u'\n'.join(body))
347 line = tpl % (magic_name, first, u'\n'.join(body))
346
348
347
349
348 def _strip_prompts(prompt1_re, prompt2_re):
350 def _strip_prompts(prompt1_re, prompt2_re):
349 """Remove matching input prompts from a block of input."""
351 """Remove matching input prompts from a block of input."""
350 line = ''
352 line = ''
351 while True:
353 while True:
352 line = (yield line)
354 line = (yield line)
353
355
354 if line is None:
356 if line is None:
355 continue
357 continue
356
358
357 m = prompt1_re.match(line)
359 m = prompt1_re.match(line)
358 if m:
360 if m:
359 while m:
361 while m:
360 line = (yield line[len(m.group(0)):])
362 line = (yield line[len(m.group(0)):])
361 if line is None:
363 if line is None:
362 break
364 break
363 m = prompt2_re.match(line)
365 m = prompt2_re.match(line)
364 else:
366 else:
365 # Prompts not in input - wait for reset
367 # Prompts not in input - wait for reset
366 while line is not None:
368 while line is not None:
367 line = (yield line)
369 line = (yield line)
368
370
369 @CoroutineInputTransformer.wrap
371 @CoroutineInputTransformer.wrap
370 def classic_prompt():
372 def classic_prompt():
371 """Strip the >>>/... prompts of the Python interactive shell."""
373 """Strip the >>>/... prompts of the Python interactive shell."""
372 prompt1_re = re.compile(r'^(>>> )')
374 prompt1_re = re.compile(r'^(>>> )')
373 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
375 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
374 return _strip_prompts(prompt1_re, prompt2_re)
376 return _strip_prompts(prompt1_re, prompt2_re)
375
377
376 @CoroutineInputTransformer.wrap
378 @CoroutineInputTransformer.wrap
377 def ipy_prompt():
379 def ipy_prompt():
378 """Strip IPython's In [1]:/...: prompts."""
380 """Strip IPython's In [1]:/...: prompts."""
379 prompt1_re = re.compile(r'^In \[\d+\]: ')
381 prompt1_re = re.compile(r'^In \[\d+\]: ')
380 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
382 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
381 return _strip_prompts(prompt1_re, prompt2_re)
383 return _strip_prompts(prompt1_re, prompt2_re)
382
384
383
385
384 @CoroutineInputTransformer.wrap
386 @CoroutineInputTransformer.wrap
385 def leading_indent():
387 def leading_indent():
386 """Remove leading indentation.
388 """Remove leading indentation.
387
389
388 If the first line starts with a spaces or tabs, the same whitespace will be
390 If the first line starts with a spaces or tabs, the same whitespace will be
389 removed from each following line until it is reset.
391 removed from each following line until it is reset.
390 """
392 """
391 space_re = re.compile(r'^[ \t]+')
393 space_re = re.compile(r'^[ \t]+')
392 line = ''
394 line = ''
393 while True:
395 while True:
394 line = (yield line)
396 line = (yield line)
395
397
396 if line is None:
398 if line is None:
397 continue
399 continue
398
400
399 m = space_re.match(line)
401 m = space_re.match(line)
400 if m:
402 if m:
401 space = m.group(0)
403 space = m.group(0)
402 while line is not None:
404 while line is not None:
403 if line.startswith(space):
405 if line.startswith(space):
404 line = line[len(space):]
406 line = line[len(space):]
405 line = (yield line)
407 line = (yield line)
406 else:
408 else:
407 # No leading spaces - wait for reset
409 # No leading spaces - wait for reset
408 while line is not None:
410 while line is not None:
409 line = (yield line)
411 line = (yield line)
410
412
411
413
412 assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
414 assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
413 r'\s*=\s*!\s*(?P<cmd>.*)')
415 r'\s*=\s*!\s*(?P<cmd>.*)')
414 assign_system_template = '%s = get_ipython().getoutput(%r)'
416 assign_system_template = '%s = get_ipython().getoutput(%r)'
415 @StatelessInputTransformer.wrap
417 @StatelessInputTransformer.wrap
416 def assign_from_system(line):
418 def assign_from_system(line):
417 """Transform assignment from system commands (e.g. files = !ls)"""
419 """Transform assignment from system commands (e.g. files = !ls)"""
418 m = assign_system_re.match(line)
420 m = assign_system_re.match(line)
419 if m is None:
421 if m is None:
420 return line
422 return line
421
423
422 return assign_system_template % m.group('lhs', 'cmd')
424 return assign_system_template % m.group('lhs', 'cmd')
423
425
424 assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
426 assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
425 r'\s*=\s*%\s*(?P<cmd>.*)')
427 r'\s*=\s*%\s*(?P<cmd>.*)')
426 assign_magic_template = '%s = get_ipython().magic(%r)'
428 assign_magic_template = '%s = get_ipython().magic(%r)'
427 @StatelessInputTransformer.wrap
429 @StatelessInputTransformer.wrap
428 def assign_from_magic(line):
430 def assign_from_magic(line):
429 """Transform assignment from magic commands (e.g. a = %who_ls)"""
431 """Transform assignment from magic commands (e.g. a = %who_ls)"""
430 m = assign_magic_re.match(line)
432 m = assign_magic_re.match(line)
431 if m is None:
433 if m is None:
432 return line
434 return line
433
435
434 return assign_magic_template % m.group('lhs', 'cmd')
436 return assign_magic_template % m.group('lhs', 'cmd')
@@ -1,394 +1,403 b''
1 import tokenize
1 import tokenize
2 import unittest
2 import unittest
3 import nose.tools as nt
3 import nose.tools as nt
4
4
5 from IPython.testing import tools as tt
5 from IPython.testing import tools as tt
6 from IPython.utils import py3compat
6 from IPython.utils import py3compat
7 u_fmt = py3compat.u_format
7 u_fmt = py3compat.u_format
8
8
9 from IPython.core import inputtransformer as ipt
9 from IPython.core import inputtransformer as ipt
10
10
11 def transform_and_reset(transformer):
11 def transform_and_reset(transformer):
12 transformer = transformer()
12 transformer = transformer()
13 def transform(inp):
13 def transform(inp):
14 try:
14 try:
15 return transformer.push(inp)
15 return transformer.push(inp)
16 finally:
16 finally:
17 transformer.reset()
17 transformer.reset()
18
18
19 return transform
19 return transform
20
20
21 # Transformer tests
21 # Transformer tests
22 def transform_checker(tests, transformer):
22 def transform_checker(tests, transformer):
23 """Utility to loop over test inputs"""
23 """Utility to loop over test inputs"""
24 transformer = transformer()
24 transformer = transformer()
25 try:
25 try:
26 for inp, tr in tests:
26 for inp, tr in tests:
27 if inp is None:
27 if inp is None:
28 out = transformer.reset()
28 out = transformer.reset()
29 else:
29 else:
30 out = transformer.push(inp)
30 out = transformer.push(inp)
31 nt.assert_equal(out, tr)
31 nt.assert_equal(out, tr)
32 finally:
32 finally:
33 transformer.reset()
33 transformer.reset()
34
34
35 # Data for all the syntax tests in the form of lists of pairs of
35 # Data for all the syntax tests in the form of lists of pairs of
36 # raw/transformed input. We store it here as a global dict so that we can use
36 # raw/transformed input. We store it here as a global dict so that we can use
37 # it both within single-function tests and also to validate the behavior of the
37 # it both within single-function tests and also to validate the behavior of the
38 # larger objects
38 # larger objects
39
39
40 syntax = \
40 syntax = \
41 dict(assign_system =
41 dict(assign_system =
42 [(i,py3compat.u_format(o)) for i,o in \
42 [(i,py3compat.u_format(o)) for i,o in \
43 [(u'a =! ls', "a = get_ipython().getoutput({u}'ls')"),
43 [(u'a =! ls', "a = get_ipython().getoutput({u}'ls')"),
44 (u'b = !ls', "b = get_ipython().getoutput({u}'ls')"),
44 (u'b = !ls', "b = get_ipython().getoutput({u}'ls')"),
45 ('x=1', 'x=1'), # normal input is unmodified
45 ('x=1', 'x=1'), # normal input is unmodified
46 (' ',' '), # blank lines are kept intact
46 (' ',' '), # blank lines are kept intact
47 ]],
47 ]],
48
48
49 assign_magic =
49 assign_magic =
50 [(i,py3compat.u_format(o)) for i,o in \
50 [(i,py3compat.u_format(o)) for i,o in \
51 [(u'a =% who', "a = get_ipython().magic({u}'who')"),
51 [(u'a =% who', "a = get_ipython().magic({u}'who')"),
52 (u'b = %who', "b = get_ipython().magic({u}'who')"),
52 (u'b = %who', "b = get_ipython().magic({u}'who')"),
53 ('x=1', 'x=1'), # normal input is unmodified
53 ('x=1', 'x=1'), # normal input is unmodified
54 (' ',' '), # blank lines are kept intact
54 (' ',' '), # blank lines are kept intact
55 ]],
55 ]],
56
56
57 classic_prompt =
57 classic_prompt =
58 [('>>> x=1', 'x=1'),
58 [('>>> x=1', 'x=1'),
59 ('x=1', 'x=1'), # normal input is unmodified
59 ('x=1', 'x=1'), # normal input is unmodified
60 (' ', ' '), # blank lines are kept intact
60 (' ', ' '), # blank lines are kept intact
61 ],
61 ],
62
62
63 ipy_prompt =
63 ipy_prompt =
64 [('In [1]: x=1', 'x=1'),
64 [('In [1]: x=1', 'x=1'),
65 ('x=1', 'x=1'), # normal input is unmodified
65 ('x=1', 'x=1'), # normal input is unmodified
66 (' ',' '), # blank lines are kept intact
66 (' ',' '), # blank lines are kept intact
67 ],
67 ],
68
68
69 # Tests for the escape transformer to leave normal code alone
69 # Tests for the escape transformer to leave normal code alone
70 escaped_noesc =
70 escaped_noesc =
71 [ (' ', ' '),
71 [ (' ', ' '),
72 ('x=1', 'x=1'),
72 ('x=1', 'x=1'),
73 ],
73 ],
74
74
75 # System calls
75 # System calls
76 escaped_shell =
76 escaped_shell =
77 [(i,py3compat.u_format(o)) for i,o in \
77 [(i,py3compat.u_format(o)) for i,o in \
78 [ (u'!ls', "get_ipython().system({u}'ls')"),
78 [ (u'!ls', "get_ipython().system({u}'ls')"),
79 # Double-escape shell, this means to capture the output of the
79 # Double-escape shell, this means to capture the output of the
80 # subprocess and return it
80 # subprocess and return it
81 (u'!!ls', "get_ipython().getoutput({u}'ls')"),
81 (u'!!ls', "get_ipython().getoutput({u}'ls')"),
82 ]],
82 ]],
83
83
84 # Help/object info
84 # Help/object info
85 escaped_help =
85 escaped_help =
86 [(i,py3compat.u_format(o)) for i,o in \
86 [(i,py3compat.u_format(o)) for i,o in \
87 [ (u'?', 'get_ipython().show_usage()'),
87 [ (u'?', 'get_ipython().show_usage()'),
88 (u'?x1', "get_ipython().magic({u}'pinfo x1')"),
88 (u'?x1', "get_ipython().magic({u}'pinfo x1')"),
89 (u'??x2', "get_ipython().magic({u}'pinfo2 x2')"),
89 (u'??x2', "get_ipython().magic({u}'pinfo2 x2')"),
90 (u'?a.*s', "get_ipython().magic({u}'psearch a.*s')"),
90 (u'?a.*s', "get_ipython().magic({u}'psearch a.*s')"),
91 (u'?%hist1', "get_ipython().magic({u}'pinfo %hist1')"),
91 (u'?%hist1', "get_ipython().magic({u}'pinfo %hist1')"),
92 (u'?%%hist2', "get_ipython().magic({u}'pinfo %%hist2')"),
92 (u'?%%hist2', "get_ipython().magic({u}'pinfo %%hist2')"),
93 (u'?abc = qwe', "get_ipython().magic({u}'pinfo abc')"),
93 (u'?abc = qwe', "get_ipython().magic({u}'pinfo abc')"),
94 ]],
94 ]],
95
95
96 end_help =
96 end_help =
97 [(i,py3compat.u_format(o)) for i,o in \
97 [(i,py3compat.u_format(o)) for i,o in \
98 [ (u'x3?', "get_ipython().magic({u}'pinfo x3')"),
98 [ (u'x3?', "get_ipython().magic({u}'pinfo x3')"),
99 (u'x4??', "get_ipython().magic({u}'pinfo2 x4')"),
99 (u'x4??', "get_ipython().magic({u}'pinfo2 x4')"),
100 (u'%hist1?', "get_ipython().magic({u}'pinfo %hist1')"),
100 (u'%hist1?', "get_ipython().magic({u}'pinfo %hist1')"),
101 (u'%hist2??', "get_ipython().magic({u}'pinfo2 %hist2')"),
101 (u'%hist2??', "get_ipython().magic({u}'pinfo2 %hist2')"),
102 (u'%%hist3?', "get_ipython().magic({u}'pinfo %%hist3')"),
102 (u'%%hist3?', "get_ipython().magic({u}'pinfo %%hist3')"),
103 (u'%%hist4??', "get_ipython().magic({u}'pinfo2 %%hist4')"),
103 (u'%%hist4??', "get_ipython().magic({u}'pinfo2 %%hist4')"),
104 (u'f*?', "get_ipython().magic({u}'psearch f*')"),
104 (u'f*?', "get_ipython().magic({u}'psearch f*')"),
105 (u'ax.*aspe*?', "get_ipython().magic({u}'psearch ax.*aspe*')"),
105 (u'ax.*aspe*?', "get_ipython().magic({u}'psearch ax.*aspe*')"),
106 (u'a = abc?', "get_ipython().set_next_input({u}'a = abc');"
106 (u'a = abc?', "get_ipython().set_next_input({u}'a = abc');"
107 "get_ipython().magic({u}'pinfo abc')"),
107 "get_ipython().magic({u}'pinfo abc')"),
108 (u'a = abc.qe??', "get_ipython().set_next_input({u}'a = abc.qe');"
108 (u'a = abc.qe??', "get_ipython().set_next_input({u}'a = abc.qe');"
109 "get_ipython().magic({u}'pinfo2 abc.qe')"),
109 "get_ipython().magic({u}'pinfo2 abc.qe')"),
110 (u'a = *.items?', "get_ipython().set_next_input({u}'a = *.items');"
110 (u'a = *.items?', "get_ipython().set_next_input({u}'a = *.items');"
111 "get_ipython().magic({u}'psearch *.items')"),
111 "get_ipython().magic({u}'psearch *.items')"),
112 (u'plot(a?', "get_ipython().set_next_input({u}'plot(a');"
112 (u'plot(a?', "get_ipython().set_next_input({u}'plot(a');"
113 "get_ipython().magic({u}'pinfo a')"),
113 "get_ipython().magic({u}'pinfo a')"),
114 (u'a*2 #comment?', 'a*2 #comment?'),
114 (u'a*2 #comment?', 'a*2 #comment?'),
115 ]],
115 ]],
116
116
117 # Explicit magic calls
117 # Explicit magic calls
118 escaped_magic =
118 escaped_magic =
119 [(i,py3compat.u_format(o)) for i,o in \
119 [(i,py3compat.u_format(o)) for i,o in \
120 [ (u'%cd', "get_ipython().magic({u}'cd')"),
120 [ (u'%cd', "get_ipython().magic({u}'cd')"),
121 (u'%cd /home', "get_ipython().magic({u}'cd /home')"),
121 (u'%cd /home', "get_ipython().magic({u}'cd /home')"),
122 # Backslashes need to be escaped.
122 # Backslashes need to be escaped.
123 (u'%cd C:\\User', "get_ipython().magic({u}'cd C:\\\\User')"),
123 (u'%cd C:\\User', "get_ipython().magic({u}'cd C:\\\\User')"),
124 (u' %magic', " get_ipython().magic({u}'magic')"),
124 (u' %magic', " get_ipython().magic({u}'magic')"),
125 ]],
125 ]],
126
126
127 # Quoting with separate arguments
127 # Quoting with separate arguments
128 escaped_quote =
128 escaped_quote =
129 [ (',f', 'f("")'),
129 [ (',f', 'f("")'),
130 (',f x', 'f("x")'),
130 (',f x', 'f("x")'),
131 (' ,f y', ' f("y")'),
131 (' ,f y', ' f("y")'),
132 (',f a b', 'f("a", "b")'),
132 (',f a b', 'f("a", "b")'),
133 ],
133 ],
134
134
135 # Quoting with single argument
135 # Quoting with single argument
136 escaped_quote2 =
136 escaped_quote2 =
137 [ (';f', 'f("")'),
137 [ (';f', 'f("")'),
138 (';f x', 'f("x")'),
138 (';f x', 'f("x")'),
139 (' ;f y', ' f("y")'),
139 (' ;f y', ' f("y")'),
140 (';f a b', 'f("a b")'),
140 (';f a b', 'f("a b")'),
141 ],
141 ],
142
142
143 # Simply apply parens
143 # Simply apply parens
144 escaped_paren =
144 escaped_paren =
145 [ ('/f', 'f()'),
145 [ ('/f', 'f()'),
146 ('/f x', 'f(x)'),
146 ('/f x', 'f(x)'),
147 (' /f y', ' f(y)'),
147 (' /f y', ' f(y)'),
148 ('/f a b', 'f(a, b)'),
148 ('/f a b', 'f(a, b)'),
149 ],
149 ],
150
150
151 # Check that we transform prompts before other transforms
151 # Check that we transform prompts before other transforms
152 mixed =
152 mixed =
153 [(i,py3compat.u_format(o)) for i,o in \
153 [(i,py3compat.u_format(o)) for i,o in \
154 [ (u'In [1]: %lsmagic', "get_ipython().magic({u}'lsmagic')"),
154 [ (u'In [1]: %lsmagic', "get_ipython().magic({u}'lsmagic')"),
155 (u'>>> %lsmagic', "get_ipython().magic({u}'lsmagic')"),
155 (u'>>> %lsmagic', "get_ipython().magic({u}'lsmagic')"),
156 (u'In [2]: !ls', "get_ipython().system({u}'ls')"),
156 (u'In [2]: !ls', "get_ipython().system({u}'ls')"),
157 (u'In [3]: abs?', "get_ipython().magic({u}'pinfo abs')"),
157 (u'In [3]: abs?', "get_ipython().magic({u}'pinfo abs')"),
158 (u'In [4]: b = %who', "b = get_ipython().magic({u}'who')"),
158 (u'In [4]: b = %who', "b = get_ipython().magic({u}'who')"),
159 ]],
159 ]],
160 )
160 )
161
161
162 # multiline syntax examples. Each of these should be a list of lists, with
162 # multiline syntax examples. Each of these should be a list of lists, with
163 # each entry itself having pairs of raw/transformed input. The union (with
163 # each entry itself having pairs of raw/transformed input. The union (with
164 # '\n'.join() of the transformed inputs is what the splitter should produce
164 # '\n'.join() of the transformed inputs is what the splitter should produce
165 # when fed the raw lines one at a time via push.
165 # when fed the raw lines one at a time via push.
166 syntax_ml = \
166 syntax_ml = \
167 dict(classic_prompt =
167 dict(classic_prompt =
168 [ [('>>> for i in range(10):','for i in range(10):'),
168 [ [('>>> for i in range(10):','for i in range(10):'),
169 ('... print i',' print i'),
169 ('... print i',' print i'),
170 ('... ', ''),
170 ('... ', ''),
171 ],
171 ],
172 [('>>> a="""','a="""'),
172 [('>>> a="""','a="""'),
173 ('... 123"""','123"""'),
173 ('... 123"""','123"""'),
174 ],
174 ],
175 [('a="""','a="""'),
175 [('a="""','a="""'),
176 ('... 123"""','... 123"""'),
176 ('... 123"""','... 123"""'),
177 ],
177 ],
178 ],
178 ],
179
179
180 ipy_prompt =
180 ipy_prompt =
181 [ [('In [24]: for i in range(10):','for i in range(10):'),
181 [ [('In [24]: for i in range(10):','for i in range(10):'),
182 (' ....: print i',' print i'),
182 (' ....: print i',' print i'),
183 (' ....: ', ''),
183 (' ....: ', ''),
184 ],
184 ],
185 [('In [2]: a="""','a="""'),
185 [('In [2]: a="""','a="""'),
186 (' ...: 123"""','123"""'),
186 (' ...: 123"""','123"""'),
187 ],
187 ],
188 [('a="""','a="""'),
188 [('a="""','a="""'),
189 (' ...: 123"""',' ...: 123"""'),
189 (' ...: 123"""',' ...: 123"""'),
190 ],
190 ],
191 ],
191 ],
192
192
193 multiline_datastructure =
193 multiline_datastructure_prompt =
194 [ [('>>> a = [1,','a = [1,'),
194 [ [('>>> a = [1,','a = [1,'),
195 ('... 2]','2]'),
195 ('... 2]','2]'),
196 ],
196 ],
197 ],
197 ],
198
199 multiline_datastructure =
200 [ [('b = ("%s"', None),
201 ('# comment', None),
202 ('%foo )', 'b = ("%s"\n# comment\n%foo )'),
203 ],
204 ],
198
205
199 leading_indent =
206 leading_indent =
200 [ [(' print "hi"','print "hi"'),
207 [ [(' print "hi"','print "hi"'),
201 ],
208 ],
202 [(' for a in range(5):','for a in range(5):'),
209 [(' for a in range(5):','for a in range(5):'),
203 (' a*2',' a*2'),
210 (' a*2',' a*2'),
204 ],
211 ],
205 [(' a="""','a="""'),
212 [(' a="""','a="""'),
206 (' 123"""','123"""'),
213 (' 123"""','123"""'),
207 ],
214 ],
208 [('a="""','a="""'),
215 [('a="""','a="""'),
209 (' 123"""',' 123"""'),
216 (' 123"""',' 123"""'),
210 ],
217 ],
211 ],
218 ],
212
219
213 cellmagic =
220 cellmagic =
214 [ [(u'%%foo a', None),
221 [ [(u'%%foo a', None),
215 (None, u_fmt("get_ipython().run_cell_magic({u}'foo', {u}'a', {u}'')")),
222 (None, u_fmt("get_ipython().run_cell_magic({u}'foo', {u}'a', {u}'')")),
216 ],
223 ],
217 [(u'%%bar 123', None),
224 [(u'%%bar 123', None),
218 (u'hello', None),
225 (u'hello', None),
219 (u'', u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
226 (u'', u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
220 ],
227 ],
221 ],
228 ],
222
229
223 escaped =
230 escaped =
224 [ [('%abc def \\', None),
231 [ [('%abc def \\', None),
225 ('ghi', u_fmt("get_ipython().magic({u}'abc def ghi')")),
232 ('ghi', u_fmt("get_ipython().magic({u}'abc def ghi')")),
226 ],
233 ],
227 [('%abc def \\', None),
234 [('%abc def \\', None),
228 ('ghi\\', None),
235 ('ghi\\', None),
229 (None, u_fmt("get_ipython().magic({u}'abc def ghi')")),
236 (None, u_fmt("get_ipython().magic({u}'abc def ghi')")),
230 ],
237 ],
231 ],
238 ],
232
239
233 assign_magic =
240 assign_magic =
234 [ [(u'a = %bc de \\', None),
241 [ [(u'a = %bc de \\', None),
235 (u'fg', u_fmt("a = get_ipython().magic({u}'bc de fg')")),
242 (u'fg', u_fmt("a = get_ipython().magic({u}'bc de fg')")),
236 ],
243 ],
237 [(u'a = %bc de \\', None),
244 [(u'a = %bc de \\', None),
238 (u'fg\\', None),
245 (u'fg\\', None),
239 (None, u_fmt("a = get_ipython().magic({u}'bc de fg')")),
246 (None, u_fmt("a = get_ipython().magic({u}'bc de fg')")),
240 ],
247 ],
241 ],
248 ],
242
249
243 assign_system =
250 assign_system =
244 [ [(u'a = !bc de \\', None),
251 [ [(u'a = !bc de \\', None),
245 (u'fg', u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
252 (u'fg', u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
246 ],
253 ],
247 [(u'a = !bc de \\', None),
254 [(u'a = !bc de \\', None),
248 (u'fg\\', None),
255 (u'fg\\', None),
249 (None, u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
256 (None, u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
250 ],
257 ],
251 ],
258 ],
252 )
259 )
253
260
254
261
255 def test_assign_system():
262 def test_assign_system():
256 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
263 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
257
264
258 def test_assign_magic():
265 def test_assign_magic():
259 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
266 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
260
267
261 def test_classic_prompt():
268 def test_classic_prompt():
262 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
269 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
263 for example in syntax_ml['classic_prompt']:
270 for example in syntax_ml['classic_prompt']:
264 transform_checker(example, ipt.classic_prompt)
271 transform_checker(example, ipt.classic_prompt)
265 for example in syntax_ml['multiline_datastructure']:
272 for example in syntax_ml['multiline_datastructure_prompt']:
266 transform_checker(example, ipt.classic_prompt)
273 transform_checker(example, ipt.classic_prompt)
267
274
268
275
269 def test_ipy_prompt():
276 def test_ipy_prompt():
270 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
277 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
271 for example in syntax_ml['ipy_prompt']:
278 for example in syntax_ml['ipy_prompt']:
272 transform_checker(example, ipt.ipy_prompt)
279 transform_checker(example, ipt.ipy_prompt)
273
280
274 def test_assemble_logical_lines():
281 def test_assemble_logical_lines():
275 tests = \
282 tests = \
276 [ [(u"a = \\", None),
283 [ [(u"a = \\", None),
277 (u"123", u"a = 123"),
284 (u"123", u"a = 123"),
278 ],
285 ],
279 [(u"a = \\", None), # Test resetting when within a multi-line string
286 [(u"a = \\", None), # Test resetting when within a multi-line string
280 (u"12 *\\", None),
287 (u"12 *\\", None),
281 (None, u"a = 12 *"),
288 (None, u"a = 12 *"),
289 ],
290 [(u"# foo\\", u"# foo\\"), # Comments can't be continued like this
282 ],
291 ],
283 ]
292 ]
284 for example in tests:
293 for example in tests:
285 transform_checker(example, ipt.assemble_logical_lines)
294 transform_checker(example, ipt.assemble_logical_lines)
286
295
287 def test_assemble_python_lines():
296 def test_assemble_python_lines():
288 tests = \
297 tests = \
289 [ [(u"a = '''", None),
298 [ [(u"a = '''", None),
290 (u"abc'''", u"a = '''\nabc'''"),
299 (u"abc'''", u"a = '''\nabc'''"),
291 ],
300 ],
292 [(u"a = '''", None), # Test resetting when within a multi-line string
301 [(u"a = '''", None), # Test resetting when within a multi-line string
293 (u"def", None),
302 (u"def", None),
294 (None, u"a = '''\ndef"),
303 (None, u"a = '''\ndef"),
295 ],
304 ],
296 [(u"a = [1,", None),
305 [(u"a = [1,", None),
297 (u"2]", u"a = [1,\n2]"),
306 (u"2]", u"a = [1,\n2]"),
298 ],
307 ],
299 [(u"a = [1,", None), # Test resetting when within a multi-line string
308 [(u"a = [1,", None), # Test resetting when within a multi-line string
300 (u"2,", None),
309 (u"2,", None),
301 (None, u"a = [1,\n2,"),
310 (None, u"a = [1,\n2,"),
302 ],
311 ],
303 ]
312 ] + syntax_ml['multiline_datastructure']
304 for example in tests:
313 for example in tests:
305 transform_checker(example, ipt.assemble_python_lines)
314 transform_checker(example, ipt.assemble_python_lines)
306
315
307
316
308 def test_help_end():
317 def test_help_end():
309 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
318 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
310
319
311 def test_escaped_noesc():
320 def test_escaped_noesc():
312 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
321 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
313
322
314
323
315 def test_escaped_shell():
324 def test_escaped_shell():
316 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
325 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
317
326
318
327
319 def test_escaped_help():
328 def test_escaped_help():
320 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
329 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
321
330
322
331
323 def test_escaped_magic():
332 def test_escaped_magic():
324 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
333 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
325
334
326
335
327 def test_escaped_quote():
336 def test_escaped_quote():
328 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
337 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
329
338
330
339
331 def test_escaped_quote2():
340 def test_escaped_quote2():
332 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
341 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
333
342
334
343
335 def test_escaped_paren():
344 def test_escaped_paren():
336 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
345 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
337
346
338
347
339 def test_cellmagic():
348 def test_cellmagic():
340 for example in syntax_ml['cellmagic']:
349 for example in syntax_ml['cellmagic']:
341 transform_checker(example, ipt.cellmagic)
350 transform_checker(example, ipt.cellmagic)
342
351
343 def test_has_comment():
352 def test_has_comment():
344 tests = [('text', False),
353 tests = [('text', False),
345 ('text #comment', True),
354 ('text #comment', True),
346 ('text #comment\n', True),
355 ('text #comment\n', True),
347 ('#comment', True),
356 ('#comment', True),
348 ('#comment\n', True),
357 ('#comment\n', True),
349 ('a = "#string"', False),
358 ('a = "#string"', False),
350 ('a = "#string" # comment', True),
359 ('a = "#string" # comment', True),
351 ('a #comment not "string"', True),
360 ('a #comment not "string"', True),
352 ]
361 ]
353 tt.check_pairs(ipt.has_comment, tests)
362 tt.check_pairs(ipt.has_comment, tests)
354
363
355 @ipt.TokenInputTransformer.wrap
364 @ipt.TokenInputTransformer.wrap
356 def decistmt(tokens):
365 def decistmt(tokens):
357 """Substitute Decimals for floats in a string of statements.
366 """Substitute Decimals for floats in a string of statements.
358
367
359 Based on an example from the tokenize module docs.
368 Based on an example from the tokenize module docs.
360 """
369 """
361 result = []
370 result = []
362 for toknum, tokval, _, _, _ in tokens:
371 for toknum, tokval, _, _, _ in tokens:
363 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
372 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
364 for newtok in [
373 for newtok in [
365 (tokenize.NAME, 'Decimal'),
374 (tokenize.NAME, 'Decimal'),
366 (tokenize.OP, '('),
375 (tokenize.OP, '('),
367 (tokenize.STRING, repr(tokval)),
376 (tokenize.STRING, repr(tokval)),
368 (tokenize.OP, ')')
377 (tokenize.OP, ')')
369 ]:
378 ]:
370 yield newtok
379 yield newtok
371 else:
380 else:
372 yield (toknum, tokval)
381 yield (toknum, tokval)
373
382
374
383
375
384
376 def test_token_input_transformer():
385 def test_token_input_transformer():
377 tests = [(u'1.2', u_fmt(u"Decimal ({u}'1.2')")),
386 tests = [(u'1.2', u_fmt(u"Decimal ({u}'1.2')")),
378 (u'"1.2"', u'"1.2"'),
387 (u'"1.2"', u'"1.2"'),
379 ]
388 ]
380 tt.check_pairs(transform_and_reset(decistmt), tests)
389 tt.check_pairs(transform_and_reset(decistmt), tests)
381 ml_tests = \
390 ml_tests = \
382 [ [(u"a = 1.2; b = '''x", None),
391 [ [(u"a = 1.2; b = '''x", None),
383 (u"y'''", u_fmt(u"a =Decimal ({u}'1.2');b ='''x\ny'''")),
392 (u"y'''", u_fmt(u"a =Decimal ({u}'1.2');b ='''x\ny'''")),
384 ],
393 ],
385 [(u"a = [1.2,", None),
394 [(u"a = [1.2,", None),
386 (u"3]", u_fmt(u"a =[Decimal ({u}'1.2'),\n3 ]")),
395 (u"3]", u_fmt(u"a =[Decimal ({u}'1.2'),\n3 ]")),
387 ],
396 ],
388 [(u"a = '''foo", None), # Test resetting when within a multi-line string
397 [(u"a = '''foo", None), # Test resetting when within a multi-line string
389 (u"bar", None),
398 (u"bar", None),
390 (None, u"a = '''foo\nbar"),
399 (None, u"a = '''foo\nbar"),
391 ],
400 ],
392 ]
401 ]
393 for example in ml_tests:
402 for example in ml_tests:
394 transform_checker(example, decistmt)
403 transform_checker(example, decistmt)
General Comments 0
You need to be logged in to leave comments. Login now