##// END OF EJS Templates
Simplify input transformers...
Thomas Kluyver -
Show More
@@ -1,712 +1,712 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_transformer,
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_transformer(),
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 + [self.assemble_logical_lines]
549 if not self.within_python_line:
549 if not self.within_python_line:
550 t += self.logical_line_transforms
550 t += 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 for t in self.transforms:
559 for t in self.transforms:
560 t.reset()
560 t.reset()
561
561
562 def flush_transformers(self):
562 def flush_transformers(self):
563 def _flush(transform, out):
563 def _flush(transform, out):
564 if out is not None:
564 if out is not None:
565 tmp = transform.push(out)
565 tmp = transform.push(out)
566 return tmp or transform.reset() or None
566 return tmp or transform.reset() or None
567 else:
567 else:
568 return transform.reset() or None
568 return transform.reset() or None
569
569
570 out = None
570 out = None
571 for t in self.transforms_in_use:
571 for t in self.transforms_in_use:
572 out = _flush(t, out)
572 out = _flush(t, out)
573
573
574 if out is not None:
574 if out is not None:
575 self._store(out)
575 self._store(out)
576
576
577 def source_raw_reset(self):
577 def source_raw_reset(self):
578 """Return input and raw source and perform a full reset.
578 """Return input and raw source and perform a full reset.
579 """
579 """
580 self.flush_transformers()
580 self.flush_transformers()
581 out = self.source
581 out = self.source
582 out_r = self.source_raw
582 out_r = self.source_raw
583 self.reset()
583 self.reset()
584 return out, out_r
584 return out, out_r
585
585
586 def source_reset(self):
586 def source_reset(self):
587 self.flush_transformers()
587 self.flush_transformers()
588 return super(IPythonInputSplitter, self).source_reset()
588 return super(IPythonInputSplitter, self).source_reset()
589
589
590 def push_accepts_more(self):
590 def push_accepts_more(self):
591 if self.transformer_accumulating:
591 if self.transformer_accumulating:
592 return True
592 return True
593 else:
593 else:
594 return super(IPythonInputSplitter, self).push_accepts_more()
594 return super(IPythonInputSplitter, self).push_accepts_more()
595
595
596 def transform_cell(self, cell):
596 def transform_cell(self, cell):
597 """Process and translate a cell of input.
597 """Process and translate a cell of input.
598 """
598 """
599 self.reset()
599 self.reset()
600 self.push(cell)
600 self.push(cell)
601 return self.source_reset()
601 return self.source_reset()
602
602
603 def push(self, lines):
603 def push(self, lines):
604 """Push one or more lines of IPython input.
604 """Push one or more lines of IPython input.
605
605
606 This stores the given lines and returns a status code indicating
606 This stores the given lines and returns a status code indicating
607 whether the code forms a complete Python block or not, after processing
607 whether the code forms a complete Python block or not, after processing
608 all input lines for special IPython syntax.
608 all input lines for special IPython syntax.
609
609
610 Any exceptions generated in compilation are swallowed, but if an
610 Any exceptions generated in compilation are swallowed, but if an
611 exception was produced, the method returns True.
611 exception was produced, the method returns True.
612
612
613 Parameters
613 Parameters
614 ----------
614 ----------
615 lines : string
615 lines : string
616 One or more lines of Python input.
616 One or more lines of Python input.
617
617
618 Returns
618 Returns
619 -------
619 -------
620 is_complete : boolean
620 is_complete : boolean
621 True if the current input source (the result of the current input
621 True if the current input source (the result of the current input
622 plus prior inputs) forms a complete Python execution block. Note that
622 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
623 this value is also stored as a private attribute (_is_complete), so it
624 can be queried at any time.
624 can be queried at any time.
625 """
625 """
626
626
627 # We must ensure all input is pure unicode
627 # We must ensure all input is pure unicode
628 lines = cast_unicode(lines, self.encoding)
628 lines = cast_unicode(lines, self.encoding)
629
629
630 # ''.splitlines() --> [], but we need to push the empty line to transformers
630 # ''.splitlines() --> [], but we need to push the empty line to transformers
631 lines_list = lines.splitlines()
631 lines_list = lines.splitlines()
632 if not lines_list:
632 if not lines_list:
633 lines_list = ['']
633 lines_list = ['']
634
634
635 # Transform logic
635 # Transform logic
636 #
636 #
637 # We only apply the line transformers to the input if we have either no
637 # 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
638 # input yet, or complete input, or if the last line of the buffer ends
639 # with ':' (opening an indented block). This prevents the accidental
639 # with ':' (opening an indented block). This prevents the accidental
640 # transformation of escapes inside multiline expressions like
640 # transformation of escapes inside multiline expressions like
641 # triple-quoted strings or parenthesized expressions.
641 # triple-quoted strings or parenthesized expressions.
642 #
642 #
643 # The last heuristic, while ugly, ensures that the first line of an
643 # The last heuristic, while ugly, ensures that the first line of an
644 # indented block is correctly transformed.
644 # indented block is correctly transformed.
645 #
645 #
646 # FIXME: try to find a cleaner approach for this last bit.
646 # FIXME: try to find a cleaner approach for this last bit.
647
647
648 # If we were in 'block' mode, since we're going to pump the parent
648 # 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
649 # 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
650 # '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
651 # by one. Note that this only matters if the input has more than one
652 # line.
652 # line.
653 changed_input_mode = False
653 changed_input_mode = False
654
654
655 if self.input_mode == 'cell':
655 if self.input_mode == 'cell':
656 self.reset()
656 self.reset()
657 changed_input_mode = True
657 changed_input_mode = True
658 saved_input_mode = 'cell'
658 saved_input_mode = 'cell'
659 self.input_mode = 'line'
659 self.input_mode = 'line'
660
660
661 # Store raw source before applying any transformations to it. Note
661 # Store raw source before applying any transformations to it. Note
662 # that this must be done *after* the reset() call that would otherwise
662 # that this must be done *after* the reset() call that would otherwise
663 # flush the buffer.
663 # flush the buffer.
664 self._store(lines, self._buffer_raw, 'source_raw')
664 self._store(lines, self._buffer_raw, 'source_raw')
665
665
666 try:
666 try:
667 for line in lines_list:
667 for line in lines_list:
668 out = self.push_line(line)
668 out = self.push_line(line)
669 finally:
669 finally:
670 if changed_input_mode:
670 if changed_input_mode:
671 self.input_mode = saved_input_mode
671 self.input_mode = saved_input_mode
672
672
673 return out
673 return out
674
674
675 def push_line(self, line):
675 def push_line(self, line):
676 buf = self._buffer
676 buf = self._buffer
677
677
678 def _accumulating(dbg):
678 def _accumulating(dbg):
679 #print(dbg)
679 #print(dbg)
680 self.transformer_accumulating = True
680 self.transformer_accumulating = True
681 return False
681 return False
682
682
683 for transformer in self.physical_line_transforms:
683 for transformer in self.physical_line_transforms:
684 line = transformer.push(line)
684 line = transformer.push(line)
685 if line is None:
685 if line is None:
686 return _accumulating(transformer)
686 return _accumulating(transformer)
687
687
688 line = self.assemble_logical_lines.push(line)
688 line = self.assemble_logical_lines.push(line)
689 if line is None:
689 if line is None:
690 return _accumulating('acc logical line')
690 return _accumulating('acc logical line')
691
691
692 if not self.within_python_line:
692 if not self.within_python_line:
693 for transformer in self.logical_line_transforms:
693 for transformer in self.logical_line_transforms:
694 line = transformer.push(line)
694 line = transformer.push(line)
695 if line is None:
695 if line is None:
696 return _accumulating(transformer)
696 return _accumulating(transformer)
697
697
698 line = self.assemble_python_lines.push(line)
698 line = self.assemble_python_lines.push(line)
699 if line is None:
699 if line is None:
700 self.within_python_line = True
700 self.within_python_line = True
701 return _accumulating('acc python line')
701 return _accumulating('acc python line')
702 else:
702 else:
703 self.within_python_line = False
703 self.within_python_line = False
704
704
705 for transformer in self.python_line_transforms:
705 for transformer in self.python_line_transforms:
706 line = transformer.push(line)
706 line = transformer.push(line)
707 if line is None:
707 if line is None:
708 return _accumulating(transformer)
708 return _accumulating(transformer)
709
709
710 #print("transformers clear") #debug
710 #print("transformers clear") #debug
711 self.transformer_accumulating = False
711 self.transformer_accumulating = False
712 return super(IPythonInputSplitter, self).push(line)
712 return super(IPythonInputSplitter, self).push(line)
@@ -1,487 +1,440 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 import tokenize
5 import tokenize
6
6
7 try:
7 try:
8 generate_tokens = tokenize.generate_tokens
8 generate_tokens = tokenize.generate_tokens
9 except AttributeError:
9 except AttributeError:
10 # Python 3. Note that we use the undocumented _tokenize because it expects
10 # Python 3. Note that we use the undocumented _tokenize because it expects
11 # strings, not bytes. See also Python issue #9969.
11 # strings, not bytes. See also Python issue #9969.
12 generate_tokens = tokenize._tokenize
12 generate_tokens = tokenize._tokenize
13
13
14 from IPython.core.splitinput import split_user_input, LineInfo
14 from IPython.core.splitinput import split_user_input, LineInfo
15 from IPython.utils.untokenize import untokenize
15 from IPython.utils.untokenize import untokenize
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Globals
18 # Globals
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # The escape sequences that define the syntax transformations IPython will
21 # The escape sequences that define the syntax transformations IPython will
22 # apply to user input. These can NOT be just changed here: many regular
22 # apply to user input. These can NOT be just changed here: many regular
23 # expressions and other parts of the code may use their hardcoded values, and
23 # expressions and other parts of the code may use their hardcoded values, and
24 # for all intents and purposes they constitute the 'IPython syntax', so they
24 # for all intents and purposes they constitute the 'IPython syntax', so they
25 # should be considered fixed.
25 # should be considered fixed.
26
26
27 ESC_SHELL = '!' # Send line to underlying system shell
27 ESC_SHELL = '!' # Send line to underlying system shell
28 ESC_SH_CAP = '!!' # Send line to system shell and capture output
28 ESC_SH_CAP = '!!' # Send line to system shell and capture output
29 ESC_HELP = '?' # Find information about object
29 ESC_HELP = '?' # Find information about object
30 ESC_HELP2 = '??' # Find extra-detailed information about object
30 ESC_HELP2 = '??' # Find extra-detailed information about object
31 ESC_MAGIC = '%' # Call magic function
31 ESC_MAGIC = '%' # Call magic function
32 ESC_MAGIC2 = '%%' # Call cell-magic function
32 ESC_MAGIC2 = '%%' # Call cell-magic function
33 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
33 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
34 ESC_QUOTE2 = ';' # Quote all args as a single string, call
34 ESC_QUOTE2 = ';' # Quote all args as a single string, call
35 ESC_PAREN = '/' # Call first argument with rest of line as arguments
35 ESC_PAREN = '/' # Call first argument with rest of line as arguments
36
36
37 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
37 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
38 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
38 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
39 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
39 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
40
40
41
41
42 class InputTransformer(object):
42 class InputTransformer(object):
43 """Abstract base class for line-based input transformers."""
43 """Abstract base class for line-based input transformers."""
44 __metaclass__ = abc.ABCMeta
44 __metaclass__ = abc.ABCMeta
45
45
46 @abc.abstractmethod
46 @abc.abstractmethod
47 def push(self, line):
47 def push(self, line):
48 """Send a line of input to the transformer, returning the transformed
48 """Send a line of input to the transformer, returning the transformed
49 input or None if the transformer is waiting for more input.
49 input or None if the transformer is waiting for more input.
50
50
51 Must be overridden by subclasses.
51 Must be overridden by subclasses.
52 """
52 """
53 pass
53 pass
54
54
55 @abc.abstractmethod
55 @abc.abstractmethod
56 def reset(self):
56 def reset(self):
57 """Return, transformed any lines that the transformer has accumulated,
57 """Return, transformed any lines that the transformer has accumulated,
58 and reset its internal state.
58 and reset its internal state.
59
59
60 Must be overridden by subclasses.
60 Must be overridden by subclasses.
61 """
61 """
62 pass
62 pass
63
63
64 # Set this to True to allow the transformer to act on lines inside strings.
65 look_in_string = False
66
67 @classmethod
64 @classmethod
68 def wrap(cls, func):
65 def wrap(cls, func):
69 """Can be used by subclasses as a decorator, to return a factory that
66 """Can be used by subclasses as a decorator, to return a factory that
70 will allow instantiation with the decorated object.
67 will allow instantiation with the decorated object.
71 """
68 """
72 @functools.wraps(func)
69 @functools.wraps(func)
73 def transformer_factory():
70 def transformer_factory():
74 transformer = cls(func)
71 return cls(func)
75 if getattr(transformer_factory, 'look_in_string', False):
76 transformer.look_in_string = True
77 return transformer
78
72
79 return transformer_factory
73 return transformer_factory
80
74
81 class StatelessInputTransformer(InputTransformer):
75 class StatelessInputTransformer(InputTransformer):
82 """Wrapper for a stateless input transformer implemented as a function."""
76 """Wrapper for a stateless input transformer implemented as a function."""
83 def __init__(self, func):
77 def __init__(self, func):
84 self.func = func
78 self.func = func
85
79
86 def __repr__(self):
80 def __repr__(self):
87 return "StatelessInputTransformer(func={!r})".format(self.func)
81 return "StatelessInputTransformer(func={!r})".format(self.func)
88
82
89 def push(self, line):
83 def push(self, line):
90 """Send a line of input to the transformer, returning the
84 """Send a line of input to the transformer, returning the
91 transformed input."""
85 transformed input."""
92 return self.func(line)
86 return self.func(line)
93
87
94 def reset(self):
88 def reset(self):
95 """No-op - exists for compatibility."""
89 """No-op - exists for compatibility."""
96 pass
90 pass
97
91
98 class CoroutineInputTransformer(InputTransformer):
92 class CoroutineInputTransformer(InputTransformer):
99 """Wrapper for an input transformer implemented as a coroutine."""
93 """Wrapper for an input transformer implemented as a coroutine."""
100 def __init__(self, coro):
94 def __init__(self, coro):
101 # Prime it
95 # Prime it
102 self.coro = coro()
96 self.coro = coro()
103 next(self.coro)
97 next(self.coro)
104
98
105 def __repr__(self):
99 def __repr__(self):
106 return "CoroutineInputTransformer(coro={!r})".format(self.coro)
100 return "CoroutineInputTransformer(coro={!r})".format(self.coro)
107
101
108 def push(self, line):
102 def push(self, line):
109 """Send a line of input to the transformer, returning the
103 """Send a line of input to the transformer, returning the
110 transformed input or None if the transformer is waiting for more
104 transformed input or None if the transformer is waiting for more
111 input.
105 input.
112 """
106 """
113 return self.coro.send(line)
107 return self.coro.send(line)
114
108
115 def reset(self):
109 def reset(self):
116 """Return, transformed any lines that the transformer has
110 """Return, transformed any lines that the transformer has
117 accumulated, and reset its internal state.
111 accumulated, and reset its internal state.
118 """
112 """
119 return self.coro.send(None)
113 return self.coro.send(None)
120
114
121 class TokenInputTransformer(InputTransformer):
115 class TokenInputTransformer(InputTransformer):
122 """Wrapper for a token-based input transformer.
116 """Wrapper for a token-based input transformer.
123
117
124 func should accept a list of tokens (5-tuples, see tokenize docs), and
118 func should accept a list of tokens (5-tuples, see tokenize docs), and
125 return an iterable which can be passed to tokenize.untokenize().
119 return an iterable which can be passed to tokenize.untokenize().
126 """
120 """
127 def __init__(self, func):
121 def __init__(self, func):
128 self.func = func
122 self.func = func
129 self.current_line = ""
123 self.current_line = ""
130 self.line_used = False
124 self.line_used = False
131 self.reset_tokenizer()
125 self.reset_tokenizer()
132
126
133 def reset_tokenizer(self):
127 def reset_tokenizer(self):
134 self.tokenizer = generate_tokens(self.get_line)
128 self.tokenizer = generate_tokens(self.get_line)
135
129
136 def get_line(self):
130 def get_line(self):
137 if self.line_used:
131 if self.line_used:
138 raise tokenize.TokenError
132 raise tokenize.TokenError
139 self.line_used = True
133 self.line_used = True
140 return self.current_line
134 return self.current_line
141
135
142 def push(self, line):
136 def push(self, line):
143 self.current_line += line + "\n"
137 self.current_line += line + "\n"
144 if self.current_line.isspace():
138 if self.current_line.isspace():
145 return self.reset()
139 return self.reset()
146
140
147 self.line_used = False
141 self.line_used = False
148 tokens = []
142 tokens = []
149 stop_at_NL = False
143 stop_at_NL = False
150 try:
144 try:
151 for intok in self.tokenizer:
145 for intok in self.tokenizer:
152 tokens.append(intok)
146 tokens.append(intok)
153 t = intok[0]
147 t = intok[0]
154 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
148 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
155 # Stop before we try to pull a line we don't have yet
149 # Stop before we try to pull a line we don't have yet
156 break
150 break
157 elif t in (tokenize.COMMENT, tokenize.ERRORTOKEN):
151 elif t in (tokenize.COMMENT, tokenize.ERRORTOKEN):
158 stop_at_NL = True
152 stop_at_NL = True
159 except tokenize.TokenError:
153 except tokenize.TokenError:
160 # Multi-line statement - stop and try again with the next line
154 # Multi-line statement - stop and try again with the next line
161 self.reset_tokenizer()
155 self.reset_tokenizer()
162 return None
156 return None
163
157
164 return self.output(tokens)
158 return self.output(tokens)
165
159
166 def output(self, tokens):
160 def output(self, tokens):
167 self.current_line = ""
161 self.current_line = ""
168 self.reset_tokenizer()
162 self.reset_tokenizer()
169 return untokenize(self.func(tokens)).rstrip('\n')
163 return untokenize(self.func(tokens)).rstrip('\n')
170
164
171 def reset(self):
165 def reset(self):
172 l = self.current_line
166 l = self.current_line
173 self.current_line = ""
167 self.current_line = ""
174 self.reset_tokenizer()
168 self.reset_tokenizer()
175 if l:
169 if l:
176 return l.rstrip('\n')
170 return l.rstrip('\n')
177
171
178 class assemble_python_lines(TokenInputTransformer):
172 class assemble_python_lines(TokenInputTransformer):
179 def __init__(self):
173 def __init__(self):
180 super(assemble_python_lines, self).__init__(None)
174 super(assemble_python_lines, self).__init__(None)
181
175
182 def output(self, tokens):
176 def output(self, tokens):
183 return self.reset()
177 return self.reset()
184
178
185 @CoroutineInputTransformer.wrap
179 @CoroutineInputTransformer.wrap
186 def assemble_logical_lines():
180 def assemble_logical_lines():
187 """Join lines following explicit line continuations (\)"""
181 """Join lines following explicit line continuations (\)"""
188 line = ''
182 line = ''
189 while True:
183 while True:
190 line = (yield line)
184 line = (yield line)
191 if not line or line.isspace():
185 if not line or line.isspace():
192 continue
186 continue
193
187
194 parts = []
188 parts = []
195 while line is not None:
189 while line is not None:
196 parts.append(line.rstrip('\\'))
190 parts.append(line.rstrip('\\'))
197 if not line.endswith('\\'):
191 if not line.endswith('\\'):
198 break
192 break
199 line = (yield None)
193 line = (yield None)
200
194
201 # Output
195 # Output
202 line = ' '.join(parts)
196 line = ' '.join(parts)
203
197
204 # Utilities
198 # Utilities
205 def _make_help_call(target, esc, lspace, next_input=None):
199 def _make_help_call(target, esc, lspace, next_input=None):
206 """Prepares a pinfo(2)/psearch call from a target name and the escape
200 """Prepares a pinfo(2)/psearch call from a target name and the escape
207 (i.e. ? or ??)"""
201 (i.e. ? or ??)"""
208 method = 'pinfo2' if esc == '??' \
202 method = 'pinfo2' if esc == '??' \
209 else 'psearch' if '*' in target \
203 else 'psearch' if '*' in target \
210 else 'pinfo'
204 else 'pinfo'
211 arg = " ".join([method, target])
205 arg = " ".join([method, target])
212 if next_input is None:
206 if next_input is None:
213 return '%sget_ipython().magic(%r)' % (lspace, arg)
207 return '%sget_ipython().magic(%r)' % (lspace, arg)
214 else:
208 else:
215 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
209 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
216 (lspace, next_input, arg)
210 (lspace, next_input, arg)
217
218 @CoroutineInputTransformer.wrap
219 def escaped_transformer():
220 """Translate lines beginning with one of IPython's escape characters.
221
211
222 This is stateful to allow magic commands etc. to be continued over several
212 # These define the transformations for the different escape characters.
223 lines using explicit line continuations (\ at the end of a line).
213 def _tr_system(line_info):
214 "Translate lines escaped with: !"
215 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
216 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
217
218 def _tr_system2(line_info):
219 "Translate lines escaped with: !!"
220 cmd = line_info.line.lstrip()[2:]
221 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
222
223 def _tr_help(line_info):
224 "Translate lines escaped with: ?/??"
225 # A naked help line should just fire the intro help screen
226 if not line_info.line[1:]:
227 return 'get_ipython().show_usage()'
228
229 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
230
231 def _tr_magic(line_info):
232 "Translate lines escaped with: %"
233 tpl = '%sget_ipython().magic(%r)'
234 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
235 return tpl % (line_info.pre, cmd)
236
237 def _tr_quote(line_info):
238 "Translate lines escaped with: ,"
239 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
240 '", "'.join(line_info.the_rest.split()) )
241
242 def _tr_quote2(line_info):
243 "Translate lines escaped with: ;"
244 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
245 line_info.the_rest)
246
247 def _tr_paren(line_info):
248 "Translate lines escaped with: /"
249 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
250 ", ".join(line_info.the_rest.split()))
251
252 tr = { ESC_SHELL : _tr_system,
253 ESC_SH_CAP : _tr_system2,
254 ESC_HELP : _tr_help,
255 ESC_HELP2 : _tr_help,
256 ESC_MAGIC : _tr_magic,
257 ESC_QUOTE : _tr_quote,
258 ESC_QUOTE2 : _tr_quote2,
259 ESC_PAREN : _tr_paren }
260
261 @StatelessInputTransformer.wrap
262 def escaped_commands(line):
263 """Transform escaped commands - %magic, !system, ?help + various autocalls.
224 """
264 """
265 if not line or line.isspace():
266 return line
267 lineinf = LineInfo(line)
268 if lineinf.esc not in tr:
269 return line
225
270
226 # These define the transformations for the different escape characters.
271 return tr[lineinf.esc](lineinf)
227 def _tr_system(line_info):
228 "Translate lines escaped with: !"
229 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
230 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
231
232 def _tr_system2(line_info):
233 "Translate lines escaped with: !!"
234 cmd = line_info.line.lstrip()[2:]
235 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
236
237 def _tr_help(line_info):
238 "Translate lines escaped with: ?/??"
239 # A naked help line should just fire the intro help screen
240 if not line_info.line[1:]:
241 return 'get_ipython().show_usage()'
242
243 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
244
245 def _tr_magic(line_info):
246 "Translate lines escaped with: %"
247 tpl = '%sget_ipython().magic(%r)'
248 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
249 return tpl % (line_info.pre, cmd)
250
251 def _tr_quote(line_info):
252 "Translate lines escaped with: ,"
253 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
254 '", "'.join(line_info.the_rest.split()) )
255
256 def _tr_quote2(line_info):
257 "Translate lines escaped with: ;"
258 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
259 line_info.the_rest)
260
261 def _tr_paren(line_info):
262 "Translate lines escaped with: /"
263 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
264 ", ".join(line_info.the_rest.split()))
265
266 tr = { ESC_SHELL : _tr_system,
267 ESC_SH_CAP : _tr_system2,
268 ESC_HELP : _tr_help,
269 ESC_HELP2 : _tr_help,
270 ESC_MAGIC : _tr_magic,
271 ESC_QUOTE : _tr_quote,
272 ESC_QUOTE2 : _tr_quote2,
273 ESC_PAREN : _tr_paren }
274
275 line = ''
276 while True:
277 line = (yield line)
278 if not line or line.isspace():
279 continue
280 lineinf = LineInfo(line)
281 if lineinf.esc not in tr:
282 continue
283
284 parts = []
285 while line is not None:
286 parts.append(line.rstrip('\\'))
287 if not line.endswith('\\'):
288 break
289 line = (yield None)
290
291 # Output
292 lineinf = LineInfo(' '.join(parts))
293 line = tr[lineinf.esc](lineinf)
294
272
295 _initial_space_re = re.compile(r'\s*')
273 _initial_space_re = re.compile(r'\s*')
296
274
297 _help_end_re = re.compile(r"""(%{0,2}
275 _help_end_re = re.compile(r"""(%{0,2}
298 [a-zA-Z_*][\w*]* # Variable name
276 [a-zA-Z_*][\w*]* # Variable name
299 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
277 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
300 )
278 )
301 (\?\??)$ # ? or ??""",
279 (\?\??)$ # ? or ??""",
302 re.VERBOSE)
280 re.VERBOSE)
303
281
304 def has_comment(src):
282 def has_comment(src):
305 """Indicate whether an input line has (i.e. ends in, or is) a comment.
283 """Indicate whether an input line has (i.e. ends in, or is) a comment.
306
284
307 This uses tokenize, so it can distinguish comments from # inside strings.
285 This uses tokenize, so it can distinguish comments from # inside strings.
308
286
309 Parameters
287 Parameters
310 ----------
288 ----------
311 src : string
289 src : string
312 A single line input string.
290 A single line input string.
313
291
314 Returns
292 Returns
315 -------
293 -------
316 Boolean: True if source has a comment.
294 Boolean: True if source has a comment.
317 """
295 """
318 readline = StringIO(src).readline
296 readline = StringIO(src).readline
319 toktypes = set()
297 toktypes = set()
320 try:
298 try:
321 for t in tokenize.generate_tokens(readline):
299 for t in tokenize.generate_tokens(readline):
322 toktypes.add(t[0])
300 toktypes.add(t[0])
323 except tokenize.TokenError:
301 except tokenize.TokenError:
324 pass
302 pass
325 return(tokenize.COMMENT in toktypes)
303 return(tokenize.COMMENT in toktypes)
326
304
327
305
328 @StatelessInputTransformer.wrap
306 @StatelessInputTransformer.wrap
329 def help_end(line):
307 def help_end(line):
330 """Translate lines with ?/?? at the end"""
308 """Translate lines with ?/?? at the end"""
331 m = _help_end_re.search(line)
309 m = _help_end_re.search(line)
332 if m is None or has_comment(line):
310 if m is None or has_comment(line):
333 return line
311 return line
334 target = m.group(1)
312 target = m.group(1)
335 esc = m.group(3)
313 esc = m.group(3)
336 lspace = _initial_space_re.match(line).group(0)
314 lspace = _initial_space_re.match(line).group(0)
337
315
338 # If we're mid-command, put it back on the next prompt for the user.
316 # If we're mid-command, put it back on the next prompt for the user.
339 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
317 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
340
318
341 return _make_help_call(target, esc, lspace, next_input)
319 return _make_help_call(target, esc, lspace, next_input)
342
320
343
321
344 @CoroutineInputTransformer.wrap
322 @CoroutineInputTransformer.wrap
345 def cellmagic():
323 def cellmagic():
346 """Captures & transforms cell magics.
324 """Captures & transforms cell magics.
347
325
348 After a cell magic is started, this stores up any lines it gets until it is
326 After a cell magic is started, this stores up any lines it gets until it is
349 reset (sent None).
327 reset (sent None).
350 """
328 """
351 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
329 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
352 cellmagic_help_re = re.compile('%%\w+\?')
330 cellmagic_help_re = re.compile('%%\w+\?')
353 line = ''
331 line = ''
354 while True:
332 while True:
355 line = (yield line)
333 line = (yield line)
356 if (not line) or (not line.startswith(ESC_MAGIC2)):
334 if (not line) or (not line.startswith(ESC_MAGIC2)):
357 continue
335 continue
358
336
359 if cellmagic_help_re.match(line):
337 if cellmagic_help_re.match(line):
360 # This case will be handled by help_end
338 # This case will be handled by help_end
361 continue
339 continue
362
340
363 first = line
341 first = line
364 body = []
342 body = []
365 line = (yield None)
343 line = (yield None)
366 while (line is not None) and (line.strip() != ''):
344 while (line is not None) and (line.strip() != ''):
367 body.append(line)
345 body.append(line)
368 line = (yield None)
346 line = (yield None)
369
347
370 # Output
348 # Output
371 magic_name, _, first = first.partition(' ')
349 magic_name, _, first = first.partition(' ')
372 magic_name = magic_name.lstrip(ESC_MAGIC2)
350 magic_name = magic_name.lstrip(ESC_MAGIC2)
373 line = tpl % (magic_name, first, u'\n'.join(body))
351 line = tpl % (magic_name, first, u'\n'.join(body))
374
352
375
353
376 def _strip_prompts(prompt1_re, prompt2_re):
354 def _strip_prompts(prompt1_re, prompt2_re):
377 """Remove matching input prompts from a block of input."""
355 """Remove matching input prompts from a block of input."""
378 line = ''
356 line = ''
379 while True:
357 while True:
380 line = (yield line)
358 line = (yield line)
381
359
382 if line is None:
360 if line is None:
383 continue
361 continue
384
362
385 m = prompt1_re.match(line)
363 m = prompt1_re.match(line)
386 if m:
364 if m:
387 while m:
365 while m:
388 line = (yield line[len(m.group(0)):])
366 line = (yield line[len(m.group(0)):])
389 if line is None:
367 if line is None:
390 break
368 break
391 m = prompt2_re.match(line)
369 m = prompt2_re.match(line)
392 else:
370 else:
393 # Prompts not in input - wait for reset
371 # Prompts not in input - wait for reset
394 while line is not None:
372 while line is not None:
395 line = (yield line)
373 line = (yield line)
396
374
397 @CoroutineInputTransformer.wrap
375 @CoroutineInputTransformer.wrap
398 def classic_prompt():
376 def classic_prompt():
399 """Strip the >>>/... prompts of the Python interactive shell."""
377 """Strip the >>>/... prompts of the Python interactive shell."""
400 prompt1_re = re.compile(r'^(>>> )')
378 prompt1_re = re.compile(r'^(>>> )')
401 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
379 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
402 return _strip_prompts(prompt1_re, prompt2_re)
380 return _strip_prompts(prompt1_re, prompt2_re)
403
381
404 classic_prompt.look_in_string = True
405
406 @CoroutineInputTransformer.wrap
382 @CoroutineInputTransformer.wrap
407 def ipy_prompt():
383 def ipy_prompt():
408 """Strip IPython's In [1]:/...: prompts."""
384 """Strip IPython's In [1]:/...: prompts."""
409 prompt1_re = re.compile(r'^In \[\d+\]: ')
385 prompt1_re = re.compile(r'^In \[\d+\]: ')
410 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
386 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
411 return _strip_prompts(prompt1_re, prompt2_re)
387 return _strip_prompts(prompt1_re, prompt2_re)
412
388
413 ipy_prompt.look_in_string = True
414
415
389
416 @CoroutineInputTransformer.wrap
390 @CoroutineInputTransformer.wrap
417 def leading_indent():
391 def leading_indent():
418 """Remove leading indentation.
392 """Remove leading indentation.
419
393
420 If the first line starts with a spaces or tabs, the same whitespace will be
394 If the first line starts with a spaces or tabs, the same whitespace will be
421 removed from each following line until it is reset.
395 removed from each following line until it is reset.
422 """
396 """
423 space_re = re.compile(r'^[ \t]+')
397 space_re = re.compile(r'^[ \t]+')
424 line = ''
398 line = ''
425 while True:
399 while True:
426 line = (yield line)
400 line = (yield line)
427
401
428 if line is None:
402 if line is None:
429 continue
403 continue
430
404
431 m = space_re.match(line)
405 m = space_re.match(line)
432 if m:
406 if m:
433 space = m.group(0)
407 space = m.group(0)
434 while line is not None:
408 while line is not None:
435 if line.startswith(space):
409 if line.startswith(space):
436 line = line[len(space):]
410 line = line[len(space):]
437 line = (yield line)
411 line = (yield line)
438 else:
412 else:
439 # No leading spaces - wait for reset
413 # No leading spaces - wait for reset
440 while line is not None:
414 while line is not None:
441 line = (yield line)
415 line = (yield line)
442
416
443 leading_indent.look_in_string = True
444
445
417
446 def _special_assignment(assignment_re, template):
418 assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
447 """Transform assignment from system & magic commands.
419 r'\s*=\s*!\s*(?P<cmd>.*)')
448
420 assign_system_template = '%s = get_ipython().getoutput(%r)'
449 This is stateful so that it can handle magic commands continued on several
421 @StatelessInputTransformer.wrap
450 lines.
422 def assign_from_system(line):
451 """
452 line = ''
453 while True:
454 line = (yield line)
455 if not line or line.isspace():
456 continue
457
458 m = assignment_re.match(line)
459 if not m:
460 continue
461
462 parts = []
463 while line is not None:
464 parts.append(line.rstrip('\\'))
465 if not line.endswith('\\'):
466 break
467 line = (yield None)
468
469 # Output
470 whole = assignment_re.match(' '.join(parts))
471 line = template % (whole.group('lhs'), whole.group('cmd'))
472
473 @CoroutineInputTransformer.wrap
474 def assign_from_system():
475 """Transform assignment from system commands (e.g. files = !ls)"""
423 """Transform assignment from system commands (e.g. files = !ls)"""
476 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
424 m = assign_system_re.match(line)
477 r'\s*=\s*!\s*(?P<cmd>.*)')
425 if m is None:
478 template = '%s = get_ipython().getoutput(%r)'
426 return line
479 return _special_assignment(assignment_re, template)
427
428 return assign_system_template % m.group('lhs', 'cmd')
480
429
481 @CoroutineInputTransformer.wrap
430 assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
482 def assign_from_magic():
431 r'\s*=\s*%\s*(?P<cmd>.*)')
432 assign_magic_template = '%s = get_ipython().magic(%r)'
433 @StatelessInputTransformer.wrap
434 def assign_from_magic(line):
483 """Transform assignment from magic commands (e.g. a = %who_ls)"""
435 """Transform assignment from magic commands (e.g. a = %who_ls)"""
484 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
436 m = assign_magic_re.match(line)
485 r'\s*=\s*%\s*(?P<cmd>.*)')
437 if m is None:
486 template = '%s = get_ipython().magic(%r)'
438 return line
487 return _special_assignment(assignment_re, template)
439
440 return assign_magic_template % m.group('lhs', 'cmd')
@@ -1,368 +1,394 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 =
194 [ [('>>> a = [1,','a = [1,'),
194 [ [('>>> a = [1,','a = [1,'),
195 ('... 2]','2]'),
195 ('... 2]','2]'),
196 ],
196 ],
197 ],
197 ],
198
198
199 leading_indent =
199 leading_indent =
200 [ [(' print "hi"','print "hi"'),
200 [ [(' print "hi"','print "hi"'),
201 ],
201 ],
202 [(' for a in range(5):','for a in range(5):'),
202 [(' for a in range(5):','for a in range(5):'),
203 (' a*2',' a*2'),
203 (' a*2',' a*2'),
204 ],
204 ],
205 [(' a="""','a="""'),
205 [(' a="""','a="""'),
206 (' 123"""','123"""'),
206 (' 123"""','123"""'),
207 ],
207 ],
208 [('a="""','a="""'),
208 [('a="""','a="""'),
209 (' 123"""',' 123"""'),
209 (' 123"""',' 123"""'),
210 ],
210 ],
211 ],
211 ],
212
212
213 cellmagic =
213 cellmagic =
214 [ [(u'%%foo a', None),
214 [ [(u'%%foo a', None),
215 (None, u_fmt("get_ipython().run_cell_magic({u}'foo', {u}'a', {u}'')")),
215 (None, u_fmt("get_ipython().run_cell_magic({u}'foo', {u}'a', {u}'')")),
216 ],
216 ],
217 [(u'%%bar 123', None),
217 [(u'%%bar 123', None),
218 (u'hello', None),
218 (u'hello', None),
219 (u'', u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
219 (u'', u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
220 ],
220 ],
221 ],
221 ],
222
222
223 escaped =
223 escaped =
224 [ [('%abc def \\', None),
224 [ [('%abc def \\', None),
225 ('ghi', u_fmt("get_ipython().magic({u}'abc def ghi')")),
225 ('ghi', u_fmt("get_ipython().magic({u}'abc def ghi')")),
226 ],
226 ],
227 [('%abc def \\', None),
227 [('%abc def \\', None),
228 ('ghi\\', None),
228 ('ghi\\', None),
229 (None, u_fmt("get_ipython().magic({u}'abc def ghi')")),
229 (None, u_fmt("get_ipython().magic({u}'abc def ghi')")),
230 ],
230 ],
231 ],
231 ],
232
232
233 assign_magic =
233 assign_magic =
234 [ [(u'a = %bc de \\', None),
234 [ [(u'a = %bc de \\', None),
235 (u'fg', u_fmt("a = get_ipython().magic({u}'bc de fg')")),
235 (u'fg', u_fmt("a = get_ipython().magic({u}'bc de fg')")),
236 ],
236 ],
237 [(u'a = %bc de \\', None),
237 [(u'a = %bc de \\', None),
238 (u'fg\\', None),
238 (u'fg\\', None),
239 (None, u_fmt("a = get_ipython().magic({u}'bc de fg')")),
239 (None, u_fmt("a = get_ipython().magic({u}'bc de fg')")),
240 ],
240 ],
241 ],
241 ],
242
242
243 assign_system =
243 assign_system =
244 [ [(u'a = !bc de \\', None),
244 [ [(u'a = !bc de \\', None),
245 (u'fg', u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
245 (u'fg', u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
246 ],
246 ],
247 [(u'a = !bc de \\', None),
247 [(u'a = !bc de \\', None),
248 (u'fg\\', None),
248 (u'fg\\', None),
249 (None, u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
249 (None, u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
250 ],
250 ],
251 ],
251 ],
252 )
252 )
253
253
254
254
255 def test_assign_system():
255 def test_assign_system():
256 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
256 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
257 for example in syntax_ml['assign_system']:
258 transform_checker(example, ipt.assign_from_system)
259
257
260 def test_assign_magic():
258 def test_assign_magic():
261 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
259 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
262 for example in syntax_ml['assign_magic']:
263 transform_checker(example, ipt.assign_from_magic)
264
265
260
266 def test_classic_prompt():
261 def test_classic_prompt():
267 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
262 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
268 for example in syntax_ml['classic_prompt']:
263 for example in syntax_ml['classic_prompt']:
269 transform_checker(example, ipt.classic_prompt)
264 transform_checker(example, ipt.classic_prompt)
270 for example in syntax_ml['multiline_datastructure']:
265 for example in syntax_ml['multiline_datastructure']:
271 transform_checker(example, ipt.classic_prompt)
266 transform_checker(example, ipt.classic_prompt)
272
267
273
268
274 def test_ipy_prompt():
269 def test_ipy_prompt():
275 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
270 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
276 for example in syntax_ml['ipy_prompt']:
271 for example in syntax_ml['ipy_prompt']:
277 transform_checker(example, ipt.ipy_prompt)
272 transform_checker(example, ipt.ipy_prompt)
278
273
274 def test_assemble_logical_lines():
275 tests = \
276 [ [(u"a = \\", None),
277 (u"123", u"a = 123"),
278 ],
279 [(u"a = \\", None), # Test resetting when within a multi-line string
280 (u"12 *\\", None),
281 (None, u"a = 12 *"),
282 ],
283 ]
284 for example in tests:
285 transform_checker(example, ipt.assemble_logical_lines)
286
287 def test_assemble_python_lines():
288 tests = \
289 [ [(u"a = '''", None),
290 (u"abc'''", u"a = '''\nabc'''"),
291 ],
292 [(u"a = '''", None), # Test resetting when within a multi-line string
293 (u"def", None),
294 (None, u"a = '''\ndef"),
295 ],
296 [(u"a = [1,", None),
297 (u"2]", u"a = [1,\n2]"),
298 ],
299 [(u"a = [1,", None), # Test resetting when within a multi-line string
300 (u"2,", None),
301 (None, u"a = [1,\n2,"),
302 ],
303 ]
304 for example in tests:
305 transform_checker(example, ipt.assemble_python_lines)
306
307
279 def test_help_end():
308 def test_help_end():
280 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
309 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
281
310
282 def test_escaped_noesc():
311 def test_escaped_noesc():
283 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_noesc'])
312 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
284
313
285
314
286 def test_escaped_shell():
315 def test_escaped_shell():
287 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_shell'])
316 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
288
317
289
318
290 def test_escaped_help():
319 def test_escaped_help():
291 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_help'])
320 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
292
321
293
322
294 def test_escaped_magic():
323 def test_escaped_magic():
295 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_magic'])
324 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
296
325
297
326
298 def test_escaped_quote():
327 def test_escaped_quote():
299 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_quote'])
328 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
300
329
301
330
302 def test_escaped_quote2():
331 def test_escaped_quote2():
303 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_quote2'])
332 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
304
333
305
334
306 def test_escaped_paren():
335 def test_escaped_paren():
307 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_paren'])
336 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
308
337
309 def test_escaped_multiline():
310 for example in syntax_ml['escaped']:
311 transform_checker(example, ipt.escaped_transformer)
312
338
313 def test_cellmagic():
339 def test_cellmagic():
314 for example in syntax_ml['cellmagic']:
340 for example in syntax_ml['cellmagic']:
315 transform_checker(example, ipt.cellmagic)
341 transform_checker(example, ipt.cellmagic)
316
342
317 def test_has_comment():
343 def test_has_comment():
318 tests = [('text', False),
344 tests = [('text', False),
319 ('text #comment', True),
345 ('text #comment', True),
320 ('text #comment\n', True),
346 ('text #comment\n', True),
321 ('#comment', True),
347 ('#comment', True),
322 ('#comment\n', True),
348 ('#comment\n', True),
323 ('a = "#string"', False),
349 ('a = "#string"', False),
324 ('a = "#string" # comment', True),
350 ('a = "#string" # comment', True),
325 ('a #comment not "string"', True),
351 ('a #comment not "string"', True),
326 ]
352 ]
327 tt.check_pairs(ipt.has_comment, tests)
353 tt.check_pairs(ipt.has_comment, tests)
328
354
329 @ipt.TokenInputTransformer.wrap
355 @ipt.TokenInputTransformer.wrap
330 def decistmt(tokens):
356 def decistmt(tokens):
331 """Substitute Decimals for floats in a string of statements.
357 """Substitute Decimals for floats in a string of statements.
332
358
333 Based on an example from the tokenize module docs.
359 Based on an example from the tokenize module docs.
334 """
360 """
335 result = []
361 result = []
336 for toknum, tokval, _, _, _ in tokens:
362 for toknum, tokval, _, _, _ in tokens:
337 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
363 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
338 for newtok in [
364 for newtok in [
339 (tokenize.NAME, 'Decimal'),
365 (tokenize.NAME, 'Decimal'),
340 (tokenize.OP, '('),
366 (tokenize.OP, '('),
341 (tokenize.STRING, repr(tokval)),
367 (tokenize.STRING, repr(tokval)),
342 (tokenize.OP, ')')
368 (tokenize.OP, ')')
343 ]:
369 ]:
344 yield newtok
370 yield newtok
345 else:
371 else:
346 yield (toknum, tokval)
372 yield (toknum, tokval)
347
373
348
374
349
375
350 def test_token_input_transformer():
376 def test_token_input_transformer():
351 tests = [(u'1.2', u_fmt(u"Decimal ({u}'1.2')")),
377 tests = [(u'1.2', u_fmt(u"Decimal ({u}'1.2')")),
352 (u'"1.2"', u'"1.2"'),
378 (u'"1.2"', u'"1.2"'),
353 ]
379 ]
354 tt.check_pairs(transform_and_reset(decistmt), tests)
380 tt.check_pairs(transform_and_reset(decistmt), tests)
355 ml_tests = \
381 ml_tests = \
356 [ [(u"a = 1.2; b = '''x", None),
382 [ [(u"a = 1.2; b = '''x", None),
357 (u"y'''", u_fmt(u"a =Decimal ({u}'1.2');b ='''x\ny'''")),
383 (u"y'''", u_fmt(u"a =Decimal ({u}'1.2');b ='''x\ny'''")),
358 ],
384 ],
359 [(u"a = [1.2,", None),
385 [(u"a = [1.2,", None),
360 (u"3]", u_fmt(u"a =[Decimal ({u}'1.2'),\n3 ]")),
386 (u"3]", u_fmt(u"a =[Decimal ({u}'1.2'),\n3 ]")),
361 ],
387 ],
362 [(u"a = '''foo", None), # Test resetting when within a multi-line string
388 [(u"a = '''foo", None), # Test resetting when within a multi-line string
363 (u"bar", None),
389 (u"bar", None),
364 (None, u"a = '''foo\nbar"),
390 (None, u"a = '''foo\nbar"),
365 ],
391 ],
366 ]
392 ]
367 for example in ml_tests:
393 for example in ml_tests:
368 transform_checker(example, decistmt)
394 transform_checker(example, decistmt)
General Comments 0
You need to be logged in to leave comments. Login now