##// END OF EJS Templates
Mark inputsplitter & inputtransformer as deprecated
Thomas Kluyver -
Show More
@@ -1,766 +1,768 b''
1 """Input handling and transformation machinery.
1 """DEPRECATED: Input handling and transformation machinery.
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
2
4
3 The first class in this module, :class:`InputSplitter`, is designed to tell when
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
4 input from a line-oriented frontend is complete and should be executed, and when
6 input from a line-oriented frontend is complete and should be executed, and when
5 the user should be prompted for another line of code instead. The name 'input
7 the user should be prompted for another line of code instead. The name 'input
6 splitter' is largely for historical reasons.
8 splitter' is largely for historical reasons.
7
9
8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
9 with full support for the extended IPython syntax (magics, system calls, etc).
11 with full support for the extended IPython syntax (magics, system calls, etc).
10 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
11 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
12 and stores the results.
14 and stores the results.
13
15
14 For more details, see the class docstrings below.
16 For more details, see the class docstrings below.
15 """
17 """
16
18
17 # Copyright (c) IPython Development Team.
19 # Copyright (c) IPython Development Team.
18 # Distributed under the terms of the Modified BSD License.
20 # Distributed under the terms of the Modified BSD License.
19 import ast
21 import ast
20 import codeop
22 import codeop
21 import io
23 import io
22 import re
24 import re
23 import sys
25 import sys
24 import tokenize
26 import tokenize
25 import warnings
27 import warnings
26
28
27 from IPython.utils.py3compat import cast_unicode
29 from IPython.utils.py3compat import cast_unicode
28 from IPython.core.inputtransformer import (leading_indent,
30 from IPython.core.inputtransformer import (leading_indent,
29 classic_prompt,
31 classic_prompt,
30 ipy_prompt,
32 ipy_prompt,
31 cellmagic,
33 cellmagic,
32 assemble_logical_lines,
34 assemble_logical_lines,
33 help_end,
35 help_end,
34 escaped_commands,
36 escaped_commands,
35 assign_from_magic,
37 assign_from_magic,
36 assign_from_system,
38 assign_from_system,
37 assemble_python_lines,
39 assemble_python_lines,
38 )
40 )
39
41
40 # These are available in this module for backwards compatibility.
42 # These are available in this module for backwards compatibility.
41 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
43 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
42 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
44 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
43 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
45 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
44
46
45 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
46 # Utilities
48 # Utilities
47 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
48
50
49 # FIXME: These are general-purpose utilities that later can be moved to the
51 # FIXME: These are general-purpose utilities that later can be moved to the
50 # general ward. Kept here for now because we're being very strict about test
52 # general ward. Kept here for now because we're being very strict about test
51 # coverage with this code, and this lets us ensure that we keep 100% coverage
53 # coverage with this code, and this lets us ensure that we keep 100% coverage
52 # while developing.
54 # while developing.
53
55
54 # compiled regexps for autoindent management
56 # compiled regexps for autoindent management
55 dedent_re = re.compile('|'.join([
57 dedent_re = re.compile('|'.join([
56 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
58 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
57 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
59 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
58 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
60 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
59 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
61 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
60 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
62 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
61 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
63 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
62 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
64 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
63 ]))
65 ]))
64 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
66 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
65
67
66 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
68 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
67 # before pure comments
69 # before pure comments
68 comment_line_re = re.compile('^\s*\#')
70 comment_line_re = re.compile('^\s*\#')
69
71
70
72
71 def num_ini_spaces(s):
73 def num_ini_spaces(s):
72 """Return the number of initial spaces in a string.
74 """Return the number of initial spaces in a string.
73
75
74 Note that tabs are counted as a single space. For now, we do *not* support
76 Note that tabs are counted as a single space. For now, we do *not* support
75 mixing of tabs and spaces in the user's input.
77 mixing of tabs and spaces in the user's input.
76
78
77 Parameters
79 Parameters
78 ----------
80 ----------
79 s : string
81 s : string
80
82
81 Returns
83 Returns
82 -------
84 -------
83 n : int
85 n : int
84 """
86 """
85
87
86 ini_spaces = ini_spaces_re.match(s)
88 ini_spaces = ini_spaces_re.match(s)
87 if ini_spaces:
89 if ini_spaces:
88 return ini_spaces.end()
90 return ini_spaces.end()
89 else:
91 else:
90 return 0
92 return 0
91
93
92 # Fake token types for partial_tokenize:
94 # Fake token types for partial_tokenize:
93 INCOMPLETE_STRING = tokenize.N_TOKENS
95 INCOMPLETE_STRING = tokenize.N_TOKENS
94 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
96 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
95
97
96 # The 2 classes below have the same API as TokenInfo, but don't try to look up
98 # The 2 classes below have the same API as TokenInfo, but don't try to look up
97 # a token type name that they won't find.
99 # a token type name that they won't find.
98 class IncompleteString:
100 class IncompleteString:
99 type = exact_type = INCOMPLETE_STRING
101 type = exact_type = INCOMPLETE_STRING
100 def __init__(self, s, start, end, line):
102 def __init__(self, s, start, end, line):
101 self.s = s
103 self.s = s
102 self.start = start
104 self.start = start
103 self.end = end
105 self.end = end
104 self.line = line
106 self.line = line
105
107
106 class InMultilineStatement:
108 class InMultilineStatement:
107 type = exact_type = IN_MULTILINE_STATEMENT
109 type = exact_type = IN_MULTILINE_STATEMENT
108 def __init__(self, pos, line):
110 def __init__(self, pos, line):
109 self.s = ''
111 self.s = ''
110 self.start = self.end = pos
112 self.start = self.end = pos
111 self.line = line
113 self.line = line
112
114
113 def partial_tokens(s):
115 def partial_tokens(s):
114 """Iterate over tokens from a possibly-incomplete string of code.
116 """Iterate over tokens from a possibly-incomplete string of code.
115
117
116 This adds two special token types: INCOMPLETE_STRING and
118 This adds two special token types: INCOMPLETE_STRING and
117 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
119 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
118 represent the two main ways for code to be incomplete.
120 represent the two main ways for code to be incomplete.
119 """
121 """
120 readline = io.StringIO(s).readline
122 readline = io.StringIO(s).readline
121 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
123 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
122 try:
124 try:
123 for token in tokenize.generate_tokens(readline):
125 for token in tokenize.generate_tokens(readline):
124 yield token
126 yield token
125 except tokenize.TokenError as e:
127 except tokenize.TokenError as e:
126 # catch EOF error
128 # catch EOF error
127 lines = s.splitlines(keepends=True)
129 lines = s.splitlines(keepends=True)
128 end = len(lines), len(lines[-1])
130 end = len(lines), len(lines[-1])
129 if 'multi-line string' in e.args[0]:
131 if 'multi-line string' in e.args[0]:
130 l, c = start = token.end
132 l, c = start = token.end
131 s = lines[l-1][c:] + ''.join(lines[l:])
133 s = lines[l-1][c:] + ''.join(lines[l:])
132 yield IncompleteString(s, start, end, lines[-1])
134 yield IncompleteString(s, start, end, lines[-1])
133 elif 'multi-line statement' in e.args[0]:
135 elif 'multi-line statement' in e.args[0]:
134 yield InMultilineStatement(end, lines[-1])
136 yield InMultilineStatement(end, lines[-1])
135 else:
137 else:
136 raise
138 raise
137
139
138 def find_next_indent(code):
140 def find_next_indent(code):
139 """Find the number of spaces for the next line of indentation"""
141 """Find the number of spaces for the next line of indentation"""
140 tokens = list(partial_tokens(code))
142 tokens = list(partial_tokens(code))
141 if tokens[-1].type == tokenize.ENDMARKER:
143 if tokens[-1].type == tokenize.ENDMARKER:
142 tokens.pop()
144 tokens.pop()
143 if not tokens:
145 if not tokens:
144 return 0
146 return 0
145 while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}):
147 while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}):
146 tokens.pop()
148 tokens.pop()
147
149
148 if tokens[-1].type == INCOMPLETE_STRING:
150 if tokens[-1].type == INCOMPLETE_STRING:
149 # Inside a multiline string
151 # Inside a multiline string
150 return 0
152 return 0
151
153
152 # Find the indents used before
154 # Find the indents used before
153 prev_indents = [0]
155 prev_indents = [0]
154 def _add_indent(n):
156 def _add_indent(n):
155 if n != prev_indents[-1]:
157 if n != prev_indents[-1]:
156 prev_indents.append(n)
158 prev_indents.append(n)
157
159
158 tokiter = iter(tokens)
160 tokiter = iter(tokens)
159 for tok in tokiter:
161 for tok in tokiter:
160 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
162 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
161 _add_indent(tok.end[1])
163 _add_indent(tok.end[1])
162 elif (tok.type == tokenize.NL):
164 elif (tok.type == tokenize.NL):
163 try:
165 try:
164 _add_indent(next(tokiter).start[1])
166 _add_indent(next(tokiter).start[1])
165 except StopIteration:
167 except StopIteration:
166 break
168 break
167
169
168 last_indent = prev_indents.pop()
170 last_indent = prev_indents.pop()
169
171
170 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
172 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
171 if tokens[-1].type == IN_MULTILINE_STATEMENT:
173 if tokens[-1].type == IN_MULTILINE_STATEMENT:
172 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
174 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
173 return last_indent + 4
175 return last_indent + 4
174 return last_indent
176 return last_indent
175
177
176 if tokens[-1].exact_type == tokenize.COLON:
178 if tokens[-1].exact_type == tokenize.COLON:
177 # Line ends with colon - indent
179 # Line ends with colon - indent
178 return last_indent + 4
180 return last_indent + 4
179
181
180 if last_indent:
182 if last_indent:
181 # Examine the last line for dedent cues - statements like return or
183 # Examine the last line for dedent cues - statements like return or
182 # raise which normally end a block of code.
184 # raise which normally end a block of code.
183 last_line_starts = 0
185 last_line_starts = 0
184 for i, tok in enumerate(tokens):
186 for i, tok in enumerate(tokens):
185 if tok.type == tokenize.NEWLINE:
187 if tok.type == tokenize.NEWLINE:
186 last_line_starts = i + 1
188 last_line_starts = i + 1
187
189
188 last_line_tokens = tokens[last_line_starts:]
190 last_line_tokens = tokens[last_line_starts:]
189 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
191 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
190 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
192 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
191 # Find the most recent indentation less than the current level
193 # Find the most recent indentation less than the current level
192 for indent in reversed(prev_indents):
194 for indent in reversed(prev_indents):
193 if indent < last_indent:
195 if indent < last_indent:
194 return indent
196 return indent
195
197
196 return last_indent
198 return last_indent
197
199
198
200
199 def last_blank(src):
201 def last_blank(src):
200 """Determine if the input source ends in a blank.
202 """Determine if the input source ends in a blank.
201
203
202 A blank is either a newline or a line consisting of whitespace.
204 A blank is either a newline or a line consisting of whitespace.
203
205
204 Parameters
206 Parameters
205 ----------
207 ----------
206 src : string
208 src : string
207 A single or multiline string.
209 A single or multiline string.
208 """
210 """
209 if not src: return False
211 if not src: return False
210 ll = src.splitlines()[-1]
212 ll = src.splitlines()[-1]
211 return (ll == '') or ll.isspace()
213 return (ll == '') or ll.isspace()
212
214
213
215
214 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
216 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
215 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
217 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
216
218
217 def last_two_blanks(src):
219 def last_two_blanks(src):
218 """Determine if the input source ends in two blanks.
220 """Determine if the input source ends in two blanks.
219
221
220 A blank is either a newline or a line consisting of whitespace.
222 A blank is either a newline or a line consisting of whitespace.
221
223
222 Parameters
224 Parameters
223 ----------
225 ----------
224 src : string
226 src : string
225 A single or multiline string.
227 A single or multiline string.
226 """
228 """
227 if not src: return False
229 if not src: return False
228 # The logic here is tricky: I couldn't get a regexp to work and pass all
230 # The logic here is tricky: I couldn't get a regexp to work and pass all
229 # the tests, so I took a different approach: split the source by lines,
231 # the tests, so I took a different approach: split the source by lines,
230 # grab the last two and prepend '###\n' as a stand-in for whatever was in
232 # grab the last two and prepend '###\n' as a stand-in for whatever was in
231 # the body before the last two lines. Then, with that structure, it's
233 # the body before the last two lines. Then, with that structure, it's
232 # possible to analyze with two regexps. Not the most elegant solution, but
234 # possible to analyze with two regexps. Not the most elegant solution, but
233 # it works. If anyone tries to change this logic, make sure to validate
235 # it works. If anyone tries to change this logic, make sure to validate
234 # the whole test suite first!
236 # the whole test suite first!
235 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
237 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
236 return (bool(last_two_blanks_re.match(new_src)) or
238 return (bool(last_two_blanks_re.match(new_src)) or
237 bool(last_two_blanks_re2.match(new_src)) )
239 bool(last_two_blanks_re2.match(new_src)) )
238
240
239
241
240 def remove_comments(src):
242 def remove_comments(src):
241 """Remove all comments from input source.
243 """Remove all comments from input source.
242
244
243 Note: comments are NOT recognized inside of strings!
245 Note: comments are NOT recognized inside of strings!
244
246
245 Parameters
247 Parameters
246 ----------
248 ----------
247 src : string
249 src : string
248 A single or multiline input string.
250 A single or multiline input string.
249
251
250 Returns
252 Returns
251 -------
253 -------
252 String with all Python comments removed.
254 String with all Python comments removed.
253 """
255 """
254
256
255 return re.sub('#.*', '', src)
257 return re.sub('#.*', '', src)
256
258
257
259
258 def get_input_encoding():
260 def get_input_encoding():
259 """Return the default standard input encoding.
261 """Return the default standard input encoding.
260
262
261 If sys.stdin has no encoding, 'ascii' is returned."""
263 If sys.stdin has no encoding, 'ascii' is returned."""
262 # There are strange environments for which sys.stdin.encoding is None. We
264 # There are strange environments for which sys.stdin.encoding is None. We
263 # ensure that a valid encoding is returned.
265 # ensure that a valid encoding is returned.
264 encoding = getattr(sys.stdin, 'encoding', None)
266 encoding = getattr(sys.stdin, 'encoding', None)
265 if encoding is None:
267 if encoding is None:
266 encoding = 'ascii'
268 encoding = 'ascii'
267 return encoding
269 return encoding
268
270
269 #-----------------------------------------------------------------------------
271 #-----------------------------------------------------------------------------
270 # Classes and functions for normal Python syntax handling
272 # Classes and functions for normal Python syntax handling
271 #-----------------------------------------------------------------------------
273 #-----------------------------------------------------------------------------
272
274
273 class InputSplitter(object):
275 class InputSplitter(object):
274 r"""An object that can accumulate lines of Python source before execution.
276 r"""An object that can accumulate lines of Python source before execution.
275
277
276 This object is designed to be fed python source line-by-line, using
278 This object is designed to be fed python source line-by-line, using
277 :meth:`push`. It will return on each push whether the currently pushed
279 :meth:`push`. It will return on each push whether the currently pushed
278 code could be executed already. In addition, it provides a method called
280 code could be executed already. In addition, it provides a method called
279 :meth:`push_accepts_more` that can be used to query whether more input
281 :meth:`push_accepts_more` that can be used to query whether more input
280 can be pushed into a single interactive block.
282 can be pushed into a single interactive block.
281
283
282 This is a simple example of how an interactive terminal-based client can use
284 This is a simple example of how an interactive terminal-based client can use
283 this tool::
285 this tool::
284
286
285 isp = InputSplitter()
287 isp = InputSplitter()
286 while isp.push_accepts_more():
288 while isp.push_accepts_more():
287 indent = ' '*isp.indent_spaces
289 indent = ' '*isp.indent_spaces
288 prompt = '>>> ' + indent
290 prompt = '>>> ' + indent
289 line = indent + raw_input(prompt)
291 line = indent + raw_input(prompt)
290 isp.push(line)
292 isp.push(line)
291 print 'Input source was:\n', isp.source_reset(),
293 print 'Input source was:\n', isp.source_reset(),
292 """
294 """
293 # A cache for storing the current indentation
295 # A cache for storing the current indentation
294 # The first value stores the most recently processed source input
296 # The first value stores the most recently processed source input
295 # The second value is the number of spaces for the current indentation
297 # The second value is the number of spaces for the current indentation
296 # If self.source matches the first value, the second value is a valid
298 # If self.source matches the first value, the second value is a valid
297 # current indentation. Otherwise, the cache is invalid and the indentation
299 # current indentation. Otherwise, the cache is invalid and the indentation
298 # must be recalculated.
300 # must be recalculated.
299 _indent_spaces_cache = None, None
301 _indent_spaces_cache = None, None
300 # String, indicating the default input encoding. It is computed by default
302 # String, indicating the default input encoding. It is computed by default
301 # at initialization time via get_input_encoding(), but it can be reset by a
303 # at initialization time via get_input_encoding(), but it can be reset by a
302 # client with specific knowledge of the encoding.
304 # client with specific knowledge of the encoding.
303 encoding = ''
305 encoding = ''
304 # String where the current full source input is stored, properly encoded.
306 # String where the current full source input is stored, properly encoded.
305 # Reading this attribute is the normal way of querying the currently pushed
307 # Reading this attribute is the normal way of querying the currently pushed
306 # source code, that has been properly encoded.
308 # source code, that has been properly encoded.
307 source = ''
309 source = ''
308 # Code object corresponding to the current source. It is automatically
310 # Code object corresponding to the current source. It is automatically
309 # synced to the source, so it can be queried at any time to obtain the code
311 # synced to the source, so it can be queried at any time to obtain the code
310 # object; it will be None if the source doesn't compile to valid Python.
312 # object; it will be None if the source doesn't compile to valid Python.
311 code = None
313 code = None
312
314
313 # Private attributes
315 # Private attributes
314
316
315 # List with lines of input accumulated so far
317 # List with lines of input accumulated so far
316 _buffer = None
318 _buffer = None
317 # Command compiler
319 # Command compiler
318 _compile = None
320 _compile = None
319 # Boolean indicating whether the current block is complete
321 # Boolean indicating whether the current block is complete
320 _is_complete = None
322 _is_complete = None
321 # Boolean indicating whether the current block has an unrecoverable syntax error
323 # Boolean indicating whether the current block has an unrecoverable syntax error
322 _is_invalid = False
324 _is_invalid = False
323
325
324 def __init__(self):
326 def __init__(self):
325 """Create a new InputSplitter instance.
327 """Create a new InputSplitter instance.
326 """
328 """
327 self._buffer = []
329 self._buffer = []
328 self._compile = codeop.CommandCompiler()
330 self._compile = codeop.CommandCompiler()
329 self.encoding = get_input_encoding()
331 self.encoding = get_input_encoding()
330
332
331 def reset(self):
333 def reset(self):
332 """Reset the input buffer and associated state."""
334 """Reset the input buffer and associated state."""
333 self._buffer[:] = []
335 self._buffer[:] = []
334 self.source = ''
336 self.source = ''
335 self.code = None
337 self.code = None
336 self._is_complete = False
338 self._is_complete = False
337 self._is_invalid = False
339 self._is_invalid = False
338
340
339 def source_reset(self):
341 def source_reset(self):
340 """Return the input source and perform a full reset.
342 """Return the input source and perform a full reset.
341 """
343 """
342 out = self.source
344 out = self.source
343 self.reset()
345 self.reset()
344 return out
346 return out
345
347
346 def check_complete(self, source):
348 def check_complete(self, source):
347 """Return whether a block of code is ready to execute, or should be continued
349 """Return whether a block of code is ready to execute, or should be continued
348
350
349 This is a non-stateful API, and will reset the state of this InputSplitter.
351 This is a non-stateful API, and will reset the state of this InputSplitter.
350
352
351 Parameters
353 Parameters
352 ----------
354 ----------
353 source : string
355 source : string
354 Python input code, which can be multiline.
356 Python input code, which can be multiline.
355
357
356 Returns
358 Returns
357 -------
359 -------
358 status : str
360 status : str
359 One of 'complete', 'incomplete', or 'invalid' if source is not a
361 One of 'complete', 'incomplete', or 'invalid' if source is not a
360 prefix of valid code.
362 prefix of valid code.
361 indent_spaces : int or None
363 indent_spaces : int or None
362 The number of spaces by which to indent the next line of code. If
364 The number of spaces by which to indent the next line of code. If
363 status is not 'incomplete', this is None.
365 status is not 'incomplete', this is None.
364 """
366 """
365 self.reset()
367 self.reset()
366 try:
368 try:
367 self.push(source)
369 self.push(source)
368 except SyntaxError:
370 except SyntaxError:
369 # Transformers in IPythonInputSplitter can raise SyntaxError,
371 # Transformers in IPythonInputSplitter can raise SyntaxError,
370 # which push() will not catch.
372 # which push() will not catch.
371 return 'invalid', None
373 return 'invalid', None
372 else:
374 else:
373 if self._is_invalid:
375 if self._is_invalid:
374 return 'invalid', None
376 return 'invalid', None
375 elif self.push_accepts_more():
377 elif self.push_accepts_more():
376 return 'incomplete', self.get_indent_spaces()
378 return 'incomplete', self.get_indent_spaces()
377 else:
379 else:
378 return 'complete', None
380 return 'complete', None
379 finally:
381 finally:
380 self.reset()
382 self.reset()
381
383
382 def push(self, lines):
384 def push(self, lines):
383 """Push one or more lines of input.
385 """Push one or more lines of input.
384
386
385 This stores the given lines and returns a status code indicating
387 This stores the given lines and returns a status code indicating
386 whether the code forms a complete Python block or not.
388 whether the code forms a complete Python block or not.
387
389
388 Any exceptions generated in compilation are swallowed, but if an
390 Any exceptions generated in compilation are swallowed, but if an
389 exception was produced, the method returns True.
391 exception was produced, the method returns True.
390
392
391 Parameters
393 Parameters
392 ----------
394 ----------
393 lines : string
395 lines : string
394 One or more lines of Python input.
396 One or more lines of Python input.
395
397
396 Returns
398 Returns
397 -------
399 -------
398 is_complete : boolean
400 is_complete : boolean
399 True if the current input source (the result of the current input
401 True if the current input source (the result of the current input
400 plus prior inputs) forms a complete Python execution block. Note that
402 plus prior inputs) forms a complete Python execution block. Note that
401 this value is also stored as a private attribute (``_is_complete``), so it
403 this value is also stored as a private attribute (``_is_complete``), so it
402 can be queried at any time.
404 can be queried at any time.
403 """
405 """
404 self._store(lines)
406 self._store(lines)
405 source = self.source
407 source = self.source
406
408
407 # Before calling _compile(), reset the code object to None so that if an
409 # Before calling _compile(), reset the code object to None so that if an
408 # exception is raised in compilation, we don't mislead by having
410 # exception is raised in compilation, we don't mislead by having
409 # inconsistent code/source attributes.
411 # inconsistent code/source attributes.
410 self.code, self._is_complete = None, None
412 self.code, self._is_complete = None, None
411 self._is_invalid = False
413 self._is_invalid = False
412
414
413 # Honor termination lines properly
415 # Honor termination lines properly
414 if source.endswith('\\\n'):
416 if source.endswith('\\\n'):
415 return False
417 return False
416
418
417 try:
419 try:
418 with warnings.catch_warnings():
420 with warnings.catch_warnings():
419 warnings.simplefilter('error', SyntaxWarning)
421 warnings.simplefilter('error', SyntaxWarning)
420 self.code = self._compile(source, symbol="exec")
422 self.code = self._compile(source, symbol="exec")
421 # Invalid syntax can produce any of a number of different errors from
423 # Invalid syntax can produce any of a number of different errors from
422 # inside the compiler, so we have to catch them all. Syntax errors
424 # inside the compiler, so we have to catch them all. Syntax errors
423 # immediately produce a 'ready' block, so the invalid Python can be
425 # immediately produce a 'ready' block, so the invalid Python can be
424 # sent to the kernel for evaluation with possible ipython
426 # sent to the kernel for evaluation with possible ipython
425 # special-syntax conversion.
427 # special-syntax conversion.
426 except (SyntaxError, OverflowError, ValueError, TypeError,
428 except (SyntaxError, OverflowError, ValueError, TypeError,
427 MemoryError, SyntaxWarning):
429 MemoryError, SyntaxWarning):
428 self._is_complete = True
430 self._is_complete = True
429 self._is_invalid = True
431 self._is_invalid = True
430 else:
432 else:
431 # Compilation didn't produce any exceptions (though it may not have
433 # Compilation didn't produce any exceptions (though it may not have
432 # given a complete code object)
434 # given a complete code object)
433 self._is_complete = self.code is not None
435 self._is_complete = self.code is not None
434
436
435 return self._is_complete
437 return self._is_complete
436
438
437 def push_accepts_more(self):
439 def push_accepts_more(self):
438 """Return whether a block of interactive input can accept more input.
440 """Return whether a block of interactive input can accept more input.
439
441
440 This method is meant to be used by line-oriented frontends, who need to
442 This method is meant to be used by line-oriented frontends, who need to
441 guess whether a block is complete or not based solely on prior and
443 guess whether a block is complete or not based solely on prior and
442 current input lines. The InputSplitter considers it has a complete
444 current input lines. The InputSplitter considers it has a complete
443 interactive block and will not accept more input when either:
445 interactive block and will not accept more input when either:
444
446
445 * A SyntaxError is raised
447 * A SyntaxError is raised
446
448
447 * The code is complete and consists of a single line or a single
449 * The code is complete and consists of a single line or a single
448 non-compound statement
450 non-compound statement
449
451
450 * The code is complete and has a blank line at the end
452 * The code is complete and has a blank line at the end
451
453
452 If the current input produces a syntax error, this method immediately
454 If the current input produces a syntax error, this method immediately
453 returns False but does *not* raise the syntax error exception, as
455 returns False but does *not* raise the syntax error exception, as
454 typically clients will want to send invalid syntax to an execution
456 typically clients will want to send invalid syntax to an execution
455 backend which might convert the invalid syntax into valid Python via
457 backend which might convert the invalid syntax into valid Python via
456 one of the dynamic IPython mechanisms.
458 one of the dynamic IPython mechanisms.
457 """
459 """
458
460
459 # With incomplete input, unconditionally accept more
461 # With incomplete input, unconditionally accept more
460 # A syntax error also sets _is_complete to True - see push()
462 # A syntax error also sets _is_complete to True - see push()
461 if not self._is_complete:
463 if not self._is_complete:
462 #print("Not complete") # debug
464 #print("Not complete") # debug
463 return True
465 return True
464
466
465 # The user can make any (complete) input execute by leaving a blank line
467 # The user can make any (complete) input execute by leaving a blank line
466 last_line = self.source.splitlines()[-1]
468 last_line = self.source.splitlines()[-1]
467 if (not last_line) or last_line.isspace():
469 if (not last_line) or last_line.isspace():
468 #print("Blank line") # debug
470 #print("Blank line") # debug
469 return False
471 return False
470
472
471 # If there's just a single line or AST node, and we're flush left, as is
473 # If there's just a single line or AST node, and we're flush left, as is
472 # the case after a simple statement such as 'a=1', we want to execute it
474 # the case after a simple statement such as 'a=1', we want to execute it
473 # straight away.
475 # straight away.
474 if self.get_indent_spaces() == 0:
476 if self.get_indent_spaces() == 0:
475 if len(self.source.splitlines()) <= 1:
477 if len(self.source.splitlines()) <= 1:
476 return False
478 return False
477
479
478 try:
480 try:
479 code_ast = ast.parse(u''.join(self._buffer))
481 code_ast = ast.parse(u''.join(self._buffer))
480 except Exception:
482 except Exception:
481 #print("Can't parse AST") # debug
483 #print("Can't parse AST") # debug
482 return False
484 return False
483 else:
485 else:
484 if len(code_ast.body) == 1 and \
486 if len(code_ast.body) == 1 and \
485 not hasattr(code_ast.body[0], 'body'):
487 not hasattr(code_ast.body[0], 'body'):
486 #print("Simple statement") # debug
488 #print("Simple statement") # debug
487 return False
489 return False
488
490
489 # General fallback - accept more code
491 # General fallback - accept more code
490 return True
492 return True
491
493
492 def get_indent_spaces(self):
494 def get_indent_spaces(self):
493 sourcefor, n = self._indent_spaces_cache
495 sourcefor, n = self._indent_spaces_cache
494 if sourcefor == self.source:
496 if sourcefor == self.source:
495 return n
497 return n
496
498
497 # self.source always has a trailing newline
499 # self.source always has a trailing newline
498 n = find_next_indent(self.source[:-1])
500 n = find_next_indent(self.source[:-1])
499 self._indent_spaces_cache = (self.source, n)
501 self._indent_spaces_cache = (self.source, n)
500 return n
502 return n
501
503
502 # Backwards compatibility. I think all code that used .indent_spaces was
504 # Backwards compatibility. I think all code that used .indent_spaces was
503 # inside IPython, but we can leave this here until IPython 7 in case any
505 # inside IPython, but we can leave this here until IPython 7 in case any
504 # other modules are using it. -TK, November 2017
506 # other modules are using it. -TK, November 2017
505 indent_spaces = property(get_indent_spaces)
507 indent_spaces = property(get_indent_spaces)
506
508
507 def _store(self, lines, buffer=None, store='source'):
509 def _store(self, lines, buffer=None, store='source'):
508 """Store one or more lines of input.
510 """Store one or more lines of input.
509
511
510 If input lines are not newline-terminated, a newline is automatically
512 If input lines are not newline-terminated, a newline is automatically
511 appended."""
513 appended."""
512
514
513 if buffer is None:
515 if buffer is None:
514 buffer = self._buffer
516 buffer = self._buffer
515
517
516 if lines.endswith('\n'):
518 if lines.endswith('\n'):
517 buffer.append(lines)
519 buffer.append(lines)
518 else:
520 else:
519 buffer.append(lines+'\n')
521 buffer.append(lines+'\n')
520 setattr(self, store, self._set_source(buffer))
522 setattr(self, store, self._set_source(buffer))
521
523
522 def _set_source(self, buffer):
524 def _set_source(self, buffer):
523 return u''.join(buffer)
525 return u''.join(buffer)
524
526
525
527
526 class IPythonInputSplitter(InputSplitter):
528 class IPythonInputSplitter(InputSplitter):
527 """An input splitter that recognizes all of IPython's special syntax."""
529 """An input splitter that recognizes all of IPython's special syntax."""
528
530
529 # String with raw, untransformed input.
531 # String with raw, untransformed input.
530 source_raw = ''
532 source_raw = ''
531
533
532 # Flag to track when a transformer has stored input that it hasn't given
534 # Flag to track when a transformer has stored input that it hasn't given
533 # back yet.
535 # back yet.
534 transformer_accumulating = False
536 transformer_accumulating = False
535
537
536 # Flag to track when assemble_python_lines has stored input that it hasn't
538 # Flag to track when assemble_python_lines has stored input that it hasn't
537 # given back yet.
539 # given back yet.
538 within_python_line = False
540 within_python_line = False
539
541
540 # Private attributes
542 # Private attributes
541
543
542 # List with lines of raw input accumulated so far.
544 # List with lines of raw input accumulated so far.
543 _buffer_raw = None
545 _buffer_raw = None
544
546
545 def __init__(self, line_input_checker=True, physical_line_transforms=None,
547 def __init__(self, line_input_checker=True, physical_line_transforms=None,
546 logical_line_transforms=None, python_line_transforms=None):
548 logical_line_transforms=None, python_line_transforms=None):
547 super(IPythonInputSplitter, self).__init__()
549 super(IPythonInputSplitter, self).__init__()
548 self._buffer_raw = []
550 self._buffer_raw = []
549 self._validate = True
551 self._validate = True
550
552
551 if physical_line_transforms is not None:
553 if physical_line_transforms is not None:
552 self.physical_line_transforms = physical_line_transforms
554 self.physical_line_transforms = physical_line_transforms
553 else:
555 else:
554 self.physical_line_transforms = [
556 self.physical_line_transforms = [
555 leading_indent(),
557 leading_indent(),
556 classic_prompt(),
558 classic_prompt(),
557 ipy_prompt(),
559 ipy_prompt(),
558 cellmagic(end_on_blank_line=line_input_checker),
560 cellmagic(end_on_blank_line=line_input_checker),
559 ]
561 ]
560
562
561 self.assemble_logical_lines = assemble_logical_lines()
563 self.assemble_logical_lines = assemble_logical_lines()
562 if logical_line_transforms is not None:
564 if logical_line_transforms is not None:
563 self.logical_line_transforms = logical_line_transforms
565 self.logical_line_transforms = logical_line_transforms
564 else:
566 else:
565 self.logical_line_transforms = [
567 self.logical_line_transforms = [
566 help_end(),
568 help_end(),
567 escaped_commands(),
569 escaped_commands(),
568 assign_from_magic(),
570 assign_from_magic(),
569 assign_from_system(),
571 assign_from_system(),
570 ]
572 ]
571
573
572 self.assemble_python_lines = assemble_python_lines()
574 self.assemble_python_lines = assemble_python_lines()
573 if python_line_transforms is not None:
575 if python_line_transforms is not None:
574 self.python_line_transforms = python_line_transforms
576 self.python_line_transforms = python_line_transforms
575 else:
577 else:
576 # We don't use any of these at present
578 # We don't use any of these at present
577 self.python_line_transforms = []
579 self.python_line_transforms = []
578
580
579 @property
581 @property
580 def transforms(self):
582 def transforms(self):
581 "Quick access to all transformers."
583 "Quick access to all transformers."
582 return self.physical_line_transforms + \
584 return self.physical_line_transforms + \
583 [self.assemble_logical_lines] + self.logical_line_transforms + \
585 [self.assemble_logical_lines] + self.logical_line_transforms + \
584 [self.assemble_python_lines] + self.python_line_transforms
586 [self.assemble_python_lines] + self.python_line_transforms
585
587
586 @property
588 @property
587 def transforms_in_use(self):
589 def transforms_in_use(self):
588 """Transformers, excluding logical line transformers if we're in a
590 """Transformers, excluding logical line transformers if we're in a
589 Python line."""
591 Python line."""
590 t = self.physical_line_transforms[:]
592 t = self.physical_line_transforms[:]
591 if not self.within_python_line:
593 if not self.within_python_line:
592 t += [self.assemble_logical_lines] + self.logical_line_transforms
594 t += [self.assemble_logical_lines] + self.logical_line_transforms
593 return t + [self.assemble_python_lines] + self.python_line_transforms
595 return t + [self.assemble_python_lines] + self.python_line_transforms
594
596
595 def reset(self):
597 def reset(self):
596 """Reset the input buffer and associated state."""
598 """Reset the input buffer and associated state."""
597 super(IPythonInputSplitter, self).reset()
599 super(IPythonInputSplitter, self).reset()
598 self._buffer_raw[:] = []
600 self._buffer_raw[:] = []
599 self.source_raw = ''
601 self.source_raw = ''
600 self.transformer_accumulating = False
602 self.transformer_accumulating = False
601 self.within_python_line = False
603 self.within_python_line = False
602
604
603 for t in self.transforms:
605 for t in self.transforms:
604 try:
606 try:
605 t.reset()
607 t.reset()
606 except SyntaxError:
608 except SyntaxError:
607 # Nothing that calls reset() expects to handle transformer
609 # Nothing that calls reset() expects to handle transformer
608 # errors
610 # errors
609 pass
611 pass
610
612
611 def flush_transformers(self):
613 def flush_transformers(self):
612 def _flush(transform, outs):
614 def _flush(transform, outs):
613 """yield transformed lines
615 """yield transformed lines
614
616
615 always strings, never None
617 always strings, never None
616
618
617 transform: the current transform
619 transform: the current transform
618 outs: an iterable of previously transformed inputs.
620 outs: an iterable of previously transformed inputs.
619 Each may be multiline, which will be passed
621 Each may be multiline, which will be passed
620 one line at a time to transform.
622 one line at a time to transform.
621 """
623 """
622 for out in outs:
624 for out in outs:
623 for line in out.splitlines():
625 for line in out.splitlines():
624 # push one line at a time
626 # push one line at a time
625 tmp = transform.push(line)
627 tmp = transform.push(line)
626 if tmp is not None:
628 if tmp is not None:
627 yield tmp
629 yield tmp
628
630
629 # reset the transform
631 # reset the transform
630 tmp = transform.reset()
632 tmp = transform.reset()
631 if tmp is not None:
633 if tmp is not None:
632 yield tmp
634 yield tmp
633
635
634 out = []
636 out = []
635 for t in self.transforms_in_use:
637 for t in self.transforms_in_use:
636 out = _flush(t, out)
638 out = _flush(t, out)
637
639
638 out = list(out)
640 out = list(out)
639 if out:
641 if out:
640 self._store('\n'.join(out))
642 self._store('\n'.join(out))
641
643
642 def raw_reset(self):
644 def raw_reset(self):
643 """Return raw input only and perform a full reset.
645 """Return raw input only and perform a full reset.
644 """
646 """
645 out = self.source_raw
647 out = self.source_raw
646 self.reset()
648 self.reset()
647 return out
649 return out
648
650
649 def source_reset(self):
651 def source_reset(self):
650 try:
652 try:
651 self.flush_transformers()
653 self.flush_transformers()
652 return self.source
654 return self.source
653 finally:
655 finally:
654 self.reset()
656 self.reset()
655
657
656 def push_accepts_more(self):
658 def push_accepts_more(self):
657 if self.transformer_accumulating:
659 if self.transformer_accumulating:
658 return True
660 return True
659 else:
661 else:
660 return super(IPythonInputSplitter, self).push_accepts_more()
662 return super(IPythonInputSplitter, self).push_accepts_more()
661
663
662 def transform_cell(self, cell):
664 def transform_cell(self, cell):
663 """Process and translate a cell of input.
665 """Process and translate a cell of input.
664 """
666 """
665 self.reset()
667 self.reset()
666 try:
668 try:
667 self.push(cell)
669 self.push(cell)
668 self.flush_transformers()
670 self.flush_transformers()
669 return self.source
671 return self.source
670 finally:
672 finally:
671 self.reset()
673 self.reset()
672
674
673 def push(self, lines):
675 def push(self, lines):
674 """Push one or more lines of IPython input.
676 """Push one or more lines of IPython input.
675
677
676 This stores the given lines and returns a status code indicating
678 This stores the given lines and returns a status code indicating
677 whether the code forms a complete Python block or not, after processing
679 whether the code forms a complete Python block or not, after processing
678 all input lines for special IPython syntax.
680 all input lines for special IPython syntax.
679
681
680 Any exceptions generated in compilation are swallowed, but if an
682 Any exceptions generated in compilation are swallowed, but if an
681 exception was produced, the method returns True.
683 exception was produced, the method returns True.
682
684
683 Parameters
685 Parameters
684 ----------
686 ----------
685 lines : string
687 lines : string
686 One or more lines of Python input.
688 One or more lines of Python input.
687
689
688 Returns
690 Returns
689 -------
691 -------
690 is_complete : boolean
692 is_complete : boolean
691 True if the current input source (the result of the current input
693 True if the current input source (the result of the current input
692 plus prior inputs) forms a complete Python execution block. Note that
694 plus prior inputs) forms a complete Python execution block. Note that
693 this value is also stored as a private attribute (_is_complete), so it
695 this value is also stored as a private attribute (_is_complete), so it
694 can be queried at any time.
696 can be queried at any time.
695 """
697 """
696
698
697 # We must ensure all input is pure unicode
699 # We must ensure all input is pure unicode
698 lines = cast_unicode(lines, self.encoding)
700 lines = cast_unicode(lines, self.encoding)
699 # ''.splitlines() --> [], but we need to push the empty line to transformers
701 # ''.splitlines() --> [], but we need to push the empty line to transformers
700 lines_list = lines.splitlines()
702 lines_list = lines.splitlines()
701 if not lines_list:
703 if not lines_list:
702 lines_list = ['']
704 lines_list = ['']
703
705
704 # Store raw source before applying any transformations to it. Note
706 # Store raw source before applying any transformations to it. Note
705 # that this must be done *after* the reset() call that would otherwise
707 # that this must be done *after* the reset() call that would otherwise
706 # flush the buffer.
708 # flush the buffer.
707 self._store(lines, self._buffer_raw, 'source_raw')
709 self._store(lines, self._buffer_raw, 'source_raw')
708
710
709 transformed_lines_list = []
711 transformed_lines_list = []
710 for line in lines_list:
712 for line in lines_list:
711 transformed = self._transform_line(line)
713 transformed = self._transform_line(line)
712 if transformed is not None:
714 if transformed is not None:
713 transformed_lines_list.append(transformed)
715 transformed_lines_list.append(transformed)
714
716
715 if transformed_lines_list:
717 if transformed_lines_list:
716 transformed_lines = '\n'.join(transformed_lines_list)
718 transformed_lines = '\n'.join(transformed_lines_list)
717 return super(IPythonInputSplitter, self).push(transformed_lines)
719 return super(IPythonInputSplitter, self).push(transformed_lines)
718 else:
720 else:
719 # Got nothing back from transformers - they must be waiting for
721 # Got nothing back from transformers - they must be waiting for
720 # more input.
722 # more input.
721 return False
723 return False
722
724
723 def _transform_line(self, line):
725 def _transform_line(self, line):
724 """Push a line of input code through the various transformers.
726 """Push a line of input code through the various transformers.
725
727
726 Returns any output from the transformers, or None if a transformer
728 Returns any output from the transformers, or None if a transformer
727 is accumulating lines.
729 is accumulating lines.
728
730
729 Sets self.transformer_accumulating as a side effect.
731 Sets self.transformer_accumulating as a side effect.
730 """
732 """
731 def _accumulating(dbg):
733 def _accumulating(dbg):
732 #print(dbg)
734 #print(dbg)
733 self.transformer_accumulating = True
735 self.transformer_accumulating = True
734 return None
736 return None
735
737
736 for transformer in self.physical_line_transforms:
738 for transformer in self.physical_line_transforms:
737 line = transformer.push(line)
739 line = transformer.push(line)
738 if line is None:
740 if line is None:
739 return _accumulating(transformer)
741 return _accumulating(transformer)
740
742
741 if not self.within_python_line:
743 if not self.within_python_line:
742 line = self.assemble_logical_lines.push(line)
744 line = self.assemble_logical_lines.push(line)
743 if line is None:
745 if line is None:
744 return _accumulating('acc logical line')
746 return _accumulating('acc logical line')
745
747
746 for transformer in self.logical_line_transforms:
748 for transformer in self.logical_line_transforms:
747 line = transformer.push(line)
749 line = transformer.push(line)
748 if line is None:
750 if line is None:
749 return _accumulating(transformer)
751 return _accumulating(transformer)
750
752
751 line = self.assemble_python_lines.push(line)
753 line = self.assemble_python_lines.push(line)
752 if line is None:
754 if line is None:
753 self.within_python_line = True
755 self.within_python_line = True
754 return _accumulating('acc python line')
756 return _accumulating('acc python line')
755 else:
757 else:
756 self.within_python_line = False
758 self.within_python_line = False
757
759
758 for transformer in self.python_line_transforms:
760 for transformer in self.python_line_transforms:
759 line = transformer.push(line)
761 line = transformer.push(line)
760 if line is None:
762 if line is None:
761 return _accumulating(transformer)
763 return _accumulating(transformer)
762
764
763 #print("transformers clear") #debug
765 #print("transformers clear") #debug
764 self.transformer_accumulating = False
766 self.transformer_accumulating = False
765 return line
767 return line
766
768
@@ -1,534 +1,536 b''
1 """Input transformer classes to support IPython special syntax.
1 """DEPRECATED: Input transformer classes to support IPython special syntax.
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
2
4
3 This includes the machinery to recognise and transform ``%magic`` commands,
5 This includes the machinery to recognise and transform ``%magic`` commands,
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
6 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
5 """
7 """
6 import abc
8 import abc
7 import functools
9 import functools
8 import re
10 import re
9 from io import StringIO
11 from io import StringIO
10
12
11 from IPython.core.splitinput import LineInfo
13 from IPython.core.splitinput import LineInfo
12 from IPython.utils import tokenize2
14 from IPython.utils import tokenize2
13 from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError
15 from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError
14
16
15 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
16 # Globals
18 # Globals
17 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
18
20
19 # The escape sequences that define the syntax transformations IPython will
21 # The escape sequences that define the syntax transformations IPython will
20 # 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
21 # 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
22 # 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
23 # should be considered fixed.
25 # should be considered fixed.
24
26
25 ESC_SHELL = '!' # Send line to underlying system shell
27 ESC_SHELL = '!' # Send line to underlying system shell
26 ESC_SH_CAP = '!!' # Send line to system shell and capture output
28 ESC_SH_CAP = '!!' # Send line to system shell and capture output
27 ESC_HELP = '?' # Find information about object
29 ESC_HELP = '?' # Find information about object
28 ESC_HELP2 = '??' # Find extra-detailed information about object
30 ESC_HELP2 = '??' # Find extra-detailed information about object
29 ESC_MAGIC = '%' # Call magic function
31 ESC_MAGIC = '%' # Call magic function
30 ESC_MAGIC2 = '%%' # Call cell-magic function
32 ESC_MAGIC2 = '%%' # Call cell-magic function
31 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
32 ESC_QUOTE2 = ';' # Quote all args as a single string, call
34 ESC_QUOTE2 = ';' # Quote all args as a single string, call
33 ESC_PAREN = '/' # Call first argument with rest of line as arguments
35 ESC_PAREN = '/' # Call first argument with rest of line as arguments
34
36
35 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
37 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
36 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
38 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
37 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
39 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
38
40
39
41
40 class InputTransformer(metaclass=abc.ABCMeta):
42 class InputTransformer(metaclass=abc.ABCMeta):
41 """Abstract base class for line-based input transformers."""
43 """Abstract base class for line-based input transformers."""
42
44
43 @abc.abstractmethod
45 @abc.abstractmethod
44 def push(self, line):
46 def push(self, line):
45 """Send a line of input to the transformer, returning the transformed
47 """Send a line of input to the transformer, returning the transformed
46 input or None if the transformer is waiting for more input.
48 input or None if the transformer is waiting for more input.
47
49
48 Must be overridden by subclasses.
50 Must be overridden by subclasses.
49
51
50 Implementations may raise ``SyntaxError`` if the input is invalid. No
52 Implementations may raise ``SyntaxError`` if the input is invalid. No
51 other exceptions may be raised.
53 other exceptions may be raised.
52 """
54 """
53 pass
55 pass
54
56
55 @abc.abstractmethod
57 @abc.abstractmethod
56 def reset(self):
58 def reset(self):
57 """Return, transformed any lines that the transformer has accumulated,
59 """Return, transformed any lines that the transformer has accumulated,
58 and reset its internal state.
60 and reset its internal state.
59
61
60 Must be overridden by subclasses.
62 Must be overridden by subclasses.
61 """
63 """
62 pass
64 pass
63
65
64 @classmethod
66 @classmethod
65 def wrap(cls, func):
67 def wrap(cls, func):
66 """Can be used by subclasses as a decorator, to return a factory that
68 """Can be used by subclasses as a decorator, to return a factory that
67 will allow instantiation with the decorated object.
69 will allow instantiation with the decorated object.
68 """
70 """
69 @functools.wraps(func)
71 @functools.wraps(func)
70 def transformer_factory(**kwargs):
72 def transformer_factory(**kwargs):
71 return cls(func, **kwargs)
73 return cls(func, **kwargs)
72
74
73 return transformer_factory
75 return transformer_factory
74
76
75 class StatelessInputTransformer(InputTransformer):
77 class StatelessInputTransformer(InputTransformer):
76 """Wrapper for a stateless input transformer implemented as a function."""
78 """Wrapper for a stateless input transformer implemented as a function."""
77 def __init__(self, func):
79 def __init__(self, func):
78 self.func = func
80 self.func = func
79
81
80 def __repr__(self):
82 def __repr__(self):
81 return "StatelessInputTransformer(func={0!r})".format(self.func)
83 return "StatelessInputTransformer(func={0!r})".format(self.func)
82
84
83 def push(self, line):
85 def push(self, line):
84 """Send a line of input to the transformer, returning the
86 """Send a line of input to the transformer, returning the
85 transformed input."""
87 transformed input."""
86 return self.func(line)
88 return self.func(line)
87
89
88 def reset(self):
90 def reset(self):
89 """No-op - exists for compatibility."""
91 """No-op - exists for compatibility."""
90 pass
92 pass
91
93
92 class CoroutineInputTransformer(InputTransformer):
94 class CoroutineInputTransformer(InputTransformer):
93 """Wrapper for an input transformer implemented as a coroutine."""
95 """Wrapper for an input transformer implemented as a coroutine."""
94 def __init__(self, coro, **kwargs):
96 def __init__(self, coro, **kwargs):
95 # Prime it
97 # Prime it
96 self.coro = coro(**kwargs)
98 self.coro = coro(**kwargs)
97 next(self.coro)
99 next(self.coro)
98
100
99 def __repr__(self):
101 def __repr__(self):
100 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
102 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
101
103
102 def push(self, line):
104 def push(self, line):
103 """Send a line of input to the transformer, returning the
105 """Send a line of input to the transformer, returning the
104 transformed input or None if the transformer is waiting for more
106 transformed input or None if the transformer is waiting for more
105 input.
107 input.
106 """
108 """
107 return self.coro.send(line)
109 return self.coro.send(line)
108
110
109 def reset(self):
111 def reset(self):
110 """Return, transformed any lines that the transformer has
112 """Return, transformed any lines that the transformer has
111 accumulated, and reset its internal state.
113 accumulated, and reset its internal state.
112 """
114 """
113 return self.coro.send(None)
115 return self.coro.send(None)
114
116
115 class TokenInputTransformer(InputTransformer):
117 class TokenInputTransformer(InputTransformer):
116 """Wrapper for a token-based input transformer.
118 """Wrapper for a token-based input transformer.
117
119
118 func should accept a list of tokens (5-tuples, see tokenize docs), and
120 func should accept a list of tokens (5-tuples, see tokenize docs), and
119 return an iterable which can be passed to tokenize.untokenize().
121 return an iterable which can be passed to tokenize.untokenize().
120 """
122 """
121 def __init__(self, func):
123 def __init__(self, func):
122 self.func = func
124 self.func = func
123 self.buf = []
125 self.buf = []
124 self.reset_tokenizer()
126 self.reset_tokenizer()
125
127
126 def reset_tokenizer(self):
128 def reset_tokenizer(self):
127 it = iter(self.buf)
129 it = iter(self.buf)
128 self.tokenizer = generate_tokens(it.__next__)
130 self.tokenizer = generate_tokens(it.__next__)
129
131
130 def push(self, line):
132 def push(self, line):
131 self.buf.append(line + '\n')
133 self.buf.append(line + '\n')
132 if all(l.isspace() for l in self.buf):
134 if all(l.isspace() for l in self.buf):
133 return self.reset()
135 return self.reset()
134
136
135 tokens = []
137 tokens = []
136 stop_at_NL = False
138 stop_at_NL = False
137 try:
139 try:
138 for intok in self.tokenizer:
140 for intok in self.tokenizer:
139 tokens.append(intok)
141 tokens.append(intok)
140 t = intok[0]
142 t = intok[0]
141 if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL):
143 if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL):
142 # Stop before we try to pull a line we don't have yet
144 # Stop before we try to pull a line we don't have yet
143 break
145 break
144 elif t == tokenize2.ERRORTOKEN:
146 elif t == tokenize2.ERRORTOKEN:
145 stop_at_NL = True
147 stop_at_NL = True
146 except TokenError:
148 except TokenError:
147 # Multi-line statement - stop and try again with the next line
149 # Multi-line statement - stop and try again with the next line
148 self.reset_tokenizer()
150 self.reset_tokenizer()
149 return None
151 return None
150
152
151 return self.output(tokens)
153 return self.output(tokens)
152
154
153 def output(self, tokens):
155 def output(self, tokens):
154 self.buf.clear()
156 self.buf.clear()
155 self.reset_tokenizer()
157 self.reset_tokenizer()
156 return untokenize(self.func(tokens)).rstrip('\n')
158 return untokenize(self.func(tokens)).rstrip('\n')
157
159
158 def reset(self):
160 def reset(self):
159 l = ''.join(self.buf)
161 l = ''.join(self.buf)
160 self.buf.clear()
162 self.buf.clear()
161 self.reset_tokenizer()
163 self.reset_tokenizer()
162 if l:
164 if l:
163 return l.rstrip('\n')
165 return l.rstrip('\n')
164
166
165 class assemble_python_lines(TokenInputTransformer):
167 class assemble_python_lines(TokenInputTransformer):
166 def __init__(self):
168 def __init__(self):
167 super(assemble_python_lines, self).__init__(None)
169 super(assemble_python_lines, self).__init__(None)
168
170
169 def output(self, tokens):
171 def output(self, tokens):
170 return self.reset()
172 return self.reset()
171
173
172 @CoroutineInputTransformer.wrap
174 @CoroutineInputTransformer.wrap
173 def assemble_logical_lines():
175 def assemble_logical_lines():
174 """Join lines following explicit line continuations (\)"""
176 """Join lines following explicit line continuations (\)"""
175 line = ''
177 line = ''
176 while True:
178 while True:
177 line = (yield line)
179 line = (yield line)
178 if not line or line.isspace():
180 if not line or line.isspace():
179 continue
181 continue
180
182
181 parts = []
183 parts = []
182 while line is not None:
184 while line is not None:
183 if line.endswith('\\') and (not has_comment(line)):
185 if line.endswith('\\') and (not has_comment(line)):
184 parts.append(line[:-1])
186 parts.append(line[:-1])
185 line = (yield None) # Get another line
187 line = (yield None) # Get another line
186 else:
188 else:
187 parts.append(line)
189 parts.append(line)
188 break
190 break
189
191
190 # Output
192 # Output
191 line = ''.join(parts)
193 line = ''.join(parts)
192
194
193 # Utilities
195 # Utilities
194 def _make_help_call(target, esc, lspace, next_input=None):
196 def _make_help_call(target, esc, lspace, next_input=None):
195 """Prepares a pinfo(2)/psearch call from a target name and the escape
197 """Prepares a pinfo(2)/psearch call from a target name and the escape
196 (i.e. ? or ??)"""
198 (i.e. ? or ??)"""
197 method = 'pinfo2' if esc == '??' \
199 method = 'pinfo2' if esc == '??' \
198 else 'psearch' if '*' in target \
200 else 'psearch' if '*' in target \
199 else 'pinfo'
201 else 'pinfo'
200 arg = " ".join([method, target])
202 arg = " ".join([method, target])
201 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
203 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
202 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
204 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
203 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
205 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
204 if next_input is None:
206 if next_input is None:
205 return '%sget_ipython().run_line_magic(%r, %r)' % (lspace, t_magic_name, t_magic_arg_s)
207 return '%sget_ipython().run_line_magic(%r, %r)' % (lspace, t_magic_name, t_magic_arg_s)
206 else:
208 else:
207 return '%sget_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
209 return '%sget_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
208 (lspace, next_input, t_magic_name, t_magic_arg_s)
210 (lspace, next_input, t_magic_name, t_magic_arg_s)
209
211
210 # These define the transformations for the different escape characters.
212 # These define the transformations for the different escape characters.
211 def _tr_system(line_info):
213 def _tr_system(line_info):
212 "Translate lines escaped with: !"
214 "Translate lines escaped with: !"
213 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
215 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
214 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
216 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
215
217
216 def _tr_system2(line_info):
218 def _tr_system2(line_info):
217 "Translate lines escaped with: !!"
219 "Translate lines escaped with: !!"
218 cmd = line_info.line.lstrip()[2:]
220 cmd = line_info.line.lstrip()[2:]
219 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
221 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
220
222
221 def _tr_help(line_info):
223 def _tr_help(line_info):
222 "Translate lines escaped with: ?/??"
224 "Translate lines escaped with: ?/??"
223 # A naked help line should just fire the intro help screen
225 # A naked help line should just fire the intro help screen
224 if not line_info.line[1:]:
226 if not line_info.line[1:]:
225 return 'get_ipython().show_usage()'
227 return 'get_ipython().show_usage()'
226
228
227 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
229 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
228
230
229 def _tr_magic(line_info):
231 def _tr_magic(line_info):
230 "Translate lines escaped with: %"
232 "Translate lines escaped with: %"
231 tpl = '%sget_ipython().run_line_magic(%r, %r)'
233 tpl = '%sget_ipython().run_line_magic(%r, %r)'
232 if line_info.line.startswith(ESC_MAGIC2):
234 if line_info.line.startswith(ESC_MAGIC2):
233 return line_info.line
235 return line_info.line
234 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
236 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
235 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
237 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
236 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
238 t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
237 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
239 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
238 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
240 return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
239
241
240 def _tr_quote(line_info):
242 def _tr_quote(line_info):
241 "Translate lines escaped with: ,"
243 "Translate lines escaped with: ,"
242 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
244 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
243 '", "'.join(line_info.the_rest.split()) )
245 '", "'.join(line_info.the_rest.split()) )
244
246
245 def _tr_quote2(line_info):
247 def _tr_quote2(line_info):
246 "Translate lines escaped with: ;"
248 "Translate lines escaped with: ;"
247 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
249 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
248 line_info.the_rest)
250 line_info.the_rest)
249
251
250 def _tr_paren(line_info):
252 def _tr_paren(line_info):
251 "Translate lines escaped with: /"
253 "Translate lines escaped with: /"
252 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
254 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
253 ", ".join(line_info.the_rest.split()))
255 ", ".join(line_info.the_rest.split()))
254
256
255 tr = { ESC_SHELL : _tr_system,
257 tr = { ESC_SHELL : _tr_system,
256 ESC_SH_CAP : _tr_system2,
258 ESC_SH_CAP : _tr_system2,
257 ESC_HELP : _tr_help,
259 ESC_HELP : _tr_help,
258 ESC_HELP2 : _tr_help,
260 ESC_HELP2 : _tr_help,
259 ESC_MAGIC : _tr_magic,
261 ESC_MAGIC : _tr_magic,
260 ESC_QUOTE : _tr_quote,
262 ESC_QUOTE : _tr_quote,
261 ESC_QUOTE2 : _tr_quote2,
263 ESC_QUOTE2 : _tr_quote2,
262 ESC_PAREN : _tr_paren }
264 ESC_PAREN : _tr_paren }
263
265
264 @StatelessInputTransformer.wrap
266 @StatelessInputTransformer.wrap
265 def escaped_commands(line):
267 def escaped_commands(line):
266 """Transform escaped commands - %magic, !system, ?help + various autocalls.
268 """Transform escaped commands - %magic, !system, ?help + various autocalls.
267 """
269 """
268 if not line or line.isspace():
270 if not line or line.isspace():
269 return line
271 return line
270 lineinf = LineInfo(line)
272 lineinf = LineInfo(line)
271 if lineinf.esc not in tr:
273 if lineinf.esc not in tr:
272 return line
274 return line
273
275
274 return tr[lineinf.esc](lineinf)
276 return tr[lineinf.esc](lineinf)
275
277
276 _initial_space_re = re.compile(r'\s*')
278 _initial_space_re = re.compile(r'\s*')
277
279
278 _help_end_re = re.compile(r"""(%{0,2}
280 _help_end_re = re.compile(r"""(%{0,2}
279 [a-zA-Z_*][\w*]* # Variable name
281 [a-zA-Z_*][\w*]* # Variable name
280 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
282 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
281 )
283 )
282 (\?\??)$ # ? or ??
284 (\?\??)$ # ? or ??
283 """,
285 """,
284 re.VERBOSE)
286 re.VERBOSE)
285
287
286 # Extra pseudotokens for multiline strings and data structures
288 # Extra pseudotokens for multiline strings and data structures
287 _MULTILINE_STRING = object()
289 _MULTILINE_STRING = object()
288 _MULTILINE_STRUCTURE = object()
290 _MULTILINE_STRUCTURE = object()
289
291
290 def _line_tokens(line):
292 def _line_tokens(line):
291 """Helper for has_comment and ends_in_comment_or_string."""
293 """Helper for has_comment and ends_in_comment_or_string."""
292 readline = StringIO(line).readline
294 readline = StringIO(line).readline
293 toktypes = set()
295 toktypes = set()
294 try:
296 try:
295 for t in generate_tokens(readline):
297 for t in generate_tokens(readline):
296 toktypes.add(t[0])
298 toktypes.add(t[0])
297 except TokenError as e:
299 except TokenError as e:
298 # There are only two cases where a TokenError is raised.
300 # There are only two cases where a TokenError is raised.
299 if 'multi-line string' in e.args[0]:
301 if 'multi-line string' in e.args[0]:
300 toktypes.add(_MULTILINE_STRING)
302 toktypes.add(_MULTILINE_STRING)
301 else:
303 else:
302 toktypes.add(_MULTILINE_STRUCTURE)
304 toktypes.add(_MULTILINE_STRUCTURE)
303 return toktypes
305 return toktypes
304
306
305 def has_comment(src):
307 def has_comment(src):
306 """Indicate whether an input line has (i.e. ends in, or is) a comment.
308 """Indicate whether an input line has (i.e. ends in, or is) a comment.
307
309
308 This uses tokenize, so it can distinguish comments from # inside strings.
310 This uses tokenize, so it can distinguish comments from # inside strings.
309
311
310 Parameters
312 Parameters
311 ----------
313 ----------
312 src : string
314 src : string
313 A single line input string.
315 A single line input string.
314
316
315 Returns
317 Returns
316 -------
318 -------
317 comment : bool
319 comment : bool
318 True if source has a comment.
320 True if source has a comment.
319 """
321 """
320 return (tokenize2.COMMENT in _line_tokens(src))
322 return (tokenize2.COMMENT in _line_tokens(src))
321
323
322 def ends_in_comment_or_string(src):
324 def ends_in_comment_or_string(src):
323 """Indicates whether or not an input line ends in a comment or within
325 """Indicates whether or not an input line ends in a comment or within
324 a multiline string.
326 a multiline string.
325
327
326 Parameters
328 Parameters
327 ----------
329 ----------
328 src : string
330 src : string
329 A single line input string.
331 A single line input string.
330
332
331 Returns
333 Returns
332 -------
334 -------
333 comment : bool
335 comment : bool
334 True if source ends in a comment or multiline string.
336 True if source ends in a comment or multiline string.
335 """
337 """
336 toktypes = _line_tokens(src)
338 toktypes = _line_tokens(src)
337 return (tokenize2.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
339 return (tokenize2.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
338
340
339
341
340 @StatelessInputTransformer.wrap
342 @StatelessInputTransformer.wrap
341 def help_end(line):
343 def help_end(line):
342 """Translate lines with ?/?? at the end"""
344 """Translate lines with ?/?? at the end"""
343 m = _help_end_re.search(line)
345 m = _help_end_re.search(line)
344 if m is None or ends_in_comment_or_string(line):
346 if m is None or ends_in_comment_or_string(line):
345 return line
347 return line
346 target = m.group(1)
348 target = m.group(1)
347 esc = m.group(3)
349 esc = m.group(3)
348 lspace = _initial_space_re.match(line).group(0)
350 lspace = _initial_space_re.match(line).group(0)
349
351
350 # If we're mid-command, put it back on the next prompt for the user.
352 # If we're mid-command, put it back on the next prompt for the user.
351 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
353 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
352
354
353 return _make_help_call(target, esc, lspace, next_input)
355 return _make_help_call(target, esc, lspace, next_input)
354
356
355
357
356 @CoroutineInputTransformer.wrap
358 @CoroutineInputTransformer.wrap
357 def cellmagic(end_on_blank_line=False):
359 def cellmagic(end_on_blank_line=False):
358 """Captures & transforms cell magics.
360 """Captures & transforms cell magics.
359
361
360 After a cell magic is started, this stores up any lines it gets until it is
362 After a cell magic is started, this stores up any lines it gets until it is
361 reset (sent None).
363 reset (sent None).
362 """
364 """
363 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
365 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
364 cellmagic_help_re = re.compile('%%\w+\?')
366 cellmagic_help_re = re.compile('%%\w+\?')
365 line = ''
367 line = ''
366 while True:
368 while True:
367 line = (yield line)
369 line = (yield line)
368 # consume leading empty lines
370 # consume leading empty lines
369 while not line:
371 while not line:
370 line = (yield line)
372 line = (yield line)
371
373
372 if not line.startswith(ESC_MAGIC2):
374 if not line.startswith(ESC_MAGIC2):
373 # This isn't a cell magic, idle waiting for reset then start over
375 # This isn't a cell magic, idle waiting for reset then start over
374 while line is not None:
376 while line is not None:
375 line = (yield line)
377 line = (yield line)
376 continue
378 continue
377
379
378 if cellmagic_help_re.match(line):
380 if cellmagic_help_re.match(line):
379 # This case will be handled by help_end
381 # This case will be handled by help_end
380 continue
382 continue
381
383
382 first = line
384 first = line
383 body = []
385 body = []
384 line = (yield None)
386 line = (yield None)
385 while (line is not None) and \
387 while (line is not None) and \
386 ((line.strip() != '') or not end_on_blank_line):
388 ((line.strip() != '') or not end_on_blank_line):
387 body.append(line)
389 body.append(line)
388 line = (yield None)
390 line = (yield None)
389
391
390 # Output
392 # Output
391 magic_name, _, first = first.partition(' ')
393 magic_name, _, first = first.partition(' ')
392 magic_name = magic_name.lstrip(ESC_MAGIC2)
394 magic_name = magic_name.lstrip(ESC_MAGIC2)
393 line = tpl % (magic_name, first, u'\n'.join(body))
395 line = tpl % (magic_name, first, u'\n'.join(body))
394
396
395
397
396 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
398 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
397 """Remove matching input prompts from a block of input.
399 """Remove matching input prompts from a block of input.
398
400
399 Parameters
401 Parameters
400 ----------
402 ----------
401 prompt_re : regular expression
403 prompt_re : regular expression
402 A regular expression matching any input prompt (including continuation)
404 A regular expression matching any input prompt (including continuation)
403 initial_re : regular expression, optional
405 initial_re : regular expression, optional
404 A regular expression matching only the initial prompt, but not continuation.
406 A regular expression matching only the initial prompt, but not continuation.
405 If no initial expression is given, prompt_re will be used everywhere.
407 If no initial expression is given, prompt_re will be used everywhere.
406 Used mainly for plain Python prompts, where the continuation prompt
408 Used mainly for plain Python prompts, where the continuation prompt
407 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
409 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
408
410
409 If initial_re and prompt_re differ,
411 If initial_re and prompt_re differ,
410 only initial_re will be tested against the first line.
412 only initial_re will be tested against the first line.
411 If any prompt is found on the first two lines,
413 If any prompt is found on the first two lines,
412 prompts will be stripped from the rest of the block.
414 prompts will be stripped from the rest of the block.
413 """
415 """
414 if initial_re is None:
416 if initial_re is None:
415 initial_re = prompt_re
417 initial_re = prompt_re
416 line = ''
418 line = ''
417 while True:
419 while True:
418 line = (yield line)
420 line = (yield line)
419
421
420 # First line of cell
422 # First line of cell
421 if line is None:
423 if line is None:
422 continue
424 continue
423 out, n1 = initial_re.subn('', line, count=1)
425 out, n1 = initial_re.subn('', line, count=1)
424 if turnoff_re and not n1:
426 if turnoff_re and not n1:
425 if turnoff_re.match(line):
427 if turnoff_re.match(line):
426 # We're in e.g. a cell magic; disable this transformer for
428 # We're in e.g. a cell magic; disable this transformer for
427 # the rest of the cell.
429 # the rest of the cell.
428 while line is not None:
430 while line is not None:
429 line = (yield line)
431 line = (yield line)
430 continue
432 continue
431
433
432 line = (yield out)
434 line = (yield out)
433
435
434 if line is None:
436 if line is None:
435 continue
437 continue
436 # check for any prompt on the second line of the cell,
438 # check for any prompt on the second line of the cell,
437 # because people often copy from just after the first prompt,
439 # because people often copy from just after the first prompt,
438 # so we might not see it in the first line.
440 # so we might not see it in the first line.
439 out, n2 = prompt_re.subn('', line, count=1)
441 out, n2 = prompt_re.subn('', line, count=1)
440 line = (yield out)
442 line = (yield out)
441
443
442 if n1 or n2:
444 if n1 or n2:
443 # Found a prompt in the first two lines - check for it in
445 # Found a prompt in the first two lines - check for it in
444 # the rest of the cell as well.
446 # the rest of the cell as well.
445 while line is not None:
447 while line is not None:
446 line = (yield prompt_re.sub('', line, count=1))
448 line = (yield prompt_re.sub('', line, count=1))
447
449
448 else:
450 else:
449 # Prompts not in input - wait for reset
451 # Prompts not in input - wait for reset
450 while line is not None:
452 while line is not None:
451 line = (yield line)
453 line = (yield line)
452
454
453 @CoroutineInputTransformer.wrap
455 @CoroutineInputTransformer.wrap
454 def classic_prompt():
456 def classic_prompt():
455 """Strip the >>>/... prompts of the Python interactive shell."""
457 """Strip the >>>/... prompts of the Python interactive shell."""
456 # FIXME: non-capturing version (?:...) usable?
458 # FIXME: non-capturing version (?:...) usable?
457 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
459 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
458 initial_re = re.compile(r'^>>>( |$)')
460 initial_re = re.compile(r'^>>>( |$)')
459 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
461 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
460 turnoff_re = re.compile(r'^[%!]')
462 turnoff_re = re.compile(r'^[%!]')
461 return _strip_prompts(prompt_re, initial_re, turnoff_re)
463 return _strip_prompts(prompt_re, initial_re, turnoff_re)
462
464
463 @CoroutineInputTransformer.wrap
465 @CoroutineInputTransformer.wrap
464 def ipy_prompt():
466 def ipy_prompt():
465 """Strip IPython's In [1]:/...: prompts."""
467 """Strip IPython's In [1]:/...: prompts."""
466 # FIXME: non-capturing version (?:...) usable?
468 # FIXME: non-capturing version (?:...) usable?
467 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
469 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
468 # Disable prompt stripping inside cell magics
470 # Disable prompt stripping inside cell magics
469 turnoff_re = re.compile(r'^%%')
471 turnoff_re = re.compile(r'^%%')
470 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
472 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
471
473
472
474
473 @CoroutineInputTransformer.wrap
475 @CoroutineInputTransformer.wrap
474 def leading_indent():
476 def leading_indent():
475 """Remove leading indentation.
477 """Remove leading indentation.
476
478
477 If the first line starts with a spaces or tabs, the same whitespace will be
479 If the first line starts with a spaces or tabs, the same whitespace will be
478 removed from each following line until it is reset.
480 removed from each following line until it is reset.
479 """
481 """
480 space_re = re.compile(r'^[ \t]+')
482 space_re = re.compile(r'^[ \t]+')
481 line = ''
483 line = ''
482 while True:
484 while True:
483 line = (yield line)
485 line = (yield line)
484
486
485 if line is None:
487 if line is None:
486 continue
488 continue
487
489
488 m = space_re.match(line)
490 m = space_re.match(line)
489 if m:
491 if m:
490 space = m.group(0)
492 space = m.group(0)
491 while line is not None:
493 while line is not None:
492 if line.startswith(space):
494 if line.startswith(space):
493 line = line[len(space):]
495 line = line[len(space):]
494 line = (yield line)
496 line = (yield line)
495 else:
497 else:
496 # No leading spaces - wait for reset
498 # No leading spaces - wait for reset
497 while line is not None:
499 while line is not None:
498 line = (yield line)
500 line = (yield line)
499
501
500
502
501 _assign_pat = \
503 _assign_pat = \
502 r'''(?P<lhs>(\s*)
504 r'''(?P<lhs>(\s*)
503 ([\w\.]+) # Initial identifier
505 ([\w\.]+) # Initial identifier
504 (\s*,\s*
506 (\s*,\s*
505 \*?[\w\.]+)* # Further identifiers for unpacking
507 \*?[\w\.]+)* # Further identifiers for unpacking
506 \s*?,? # Trailing comma
508 \s*?,? # Trailing comma
507 )
509 )
508 \s*=\s*
510 \s*=\s*
509 '''
511 '''
510
512
511 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
513 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
512 assign_system_template = '%s = get_ipython().getoutput(%r)'
514 assign_system_template = '%s = get_ipython().getoutput(%r)'
513 @StatelessInputTransformer.wrap
515 @StatelessInputTransformer.wrap
514 def assign_from_system(line):
516 def assign_from_system(line):
515 """Transform assignment from system commands (e.g. files = !ls)"""
517 """Transform assignment from system commands (e.g. files = !ls)"""
516 m = assign_system_re.match(line)
518 m = assign_system_re.match(line)
517 if m is None:
519 if m is None:
518 return line
520 return line
519
521
520 return assign_system_template % m.group('lhs', 'cmd')
522 return assign_system_template % m.group('lhs', 'cmd')
521
523
522 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
524 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
523 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
525 assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
524 @StatelessInputTransformer.wrap
526 @StatelessInputTransformer.wrap
525 def assign_from_magic(line):
527 def assign_from_magic(line):
526 """Transform assignment from magic commands (e.g. a = %who_ls)"""
528 """Transform assignment from magic commands (e.g. a = %who_ls)"""
527 m = assign_magic_re.match(line)
529 m = assign_magic_re.match(line)
528 if m is None:
530 if m is None:
529 return line
531 return line
530 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
532 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
531 m_lhs, m_cmd = m.group('lhs', 'cmd')
533 m_lhs, m_cmd = m.group('lhs', 'cmd')
532 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
534 t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
533 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
535 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
534 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
536 return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
@@ -1,525 +1,534 b''
1 """Input transformer machinery to support IPython special syntax.
2
3 This includes the machinery to recognise and transform ``%magic`` commands,
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
5 """
6
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
9
1 from codeop import compile_command
10 from codeop import compile_command
2 import re
11 import re
3 from typing import List, Tuple
12 from typing import List, Tuple
4 from IPython.utils import tokenize2
13 from IPython.utils import tokenize2
5 from IPython.utils.tokenutil import generate_tokens
14 from IPython.utils.tokenutil import generate_tokens
6
15
7 _indent_re = re.compile(r'^[ \t]+')
16 _indent_re = re.compile(r'^[ \t]+')
8
17
9 def leading_indent(lines):
18 def leading_indent(lines):
10 """Remove leading indentation.
19 """Remove leading indentation.
11
20
12 If the first line starts with a spaces or tabs, the same whitespace will be
21 If the first line starts with a spaces or tabs, the same whitespace will be
13 removed from each following line.
22 removed from each following line.
14 """
23 """
15 m = _indent_re.match(lines[0])
24 m = _indent_re.match(lines[0])
16 if not m:
25 if not m:
17 return lines
26 return lines
18 space = m.group(0)
27 space = m.group(0)
19 n = len(space)
28 n = len(space)
20 return [l[n:] if l.startswith(space) else l
29 return [l[n:] if l.startswith(space) else l
21 for l in lines]
30 for l in lines]
22
31
23 class PromptStripper:
32 class PromptStripper:
24 """Remove matching input prompts from a block of input.
33 """Remove matching input prompts from a block of input.
25
34
26 Parameters
35 Parameters
27 ----------
36 ----------
28 prompt_re : regular expression
37 prompt_re : regular expression
29 A regular expression matching any input prompt (including continuation)
38 A regular expression matching any input prompt (including continuation)
30 initial_re : regular expression, optional
39 initial_re : regular expression, optional
31 A regular expression matching only the initial prompt, but not continuation.
40 A regular expression matching only the initial prompt, but not continuation.
32 If no initial expression is given, prompt_re will be used everywhere.
41 If no initial expression is given, prompt_re will be used everywhere.
33 Used mainly for plain Python prompts, where the continuation prompt
42 Used mainly for plain Python prompts, where the continuation prompt
34 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
43 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
35
44
36 If initial_re and prompt_re differ,
45 If initial_re and prompt_re differ,
37 only initial_re will be tested against the first line.
46 only initial_re will be tested against the first line.
38 If any prompt is found on the first two lines,
47 If any prompt is found on the first two lines,
39 prompts will be stripped from the rest of the block.
48 prompts will be stripped from the rest of the block.
40 """
49 """
41 def __init__(self, prompt_re, initial_re=None):
50 def __init__(self, prompt_re, initial_re=None):
42 self.prompt_re = prompt_re
51 self.prompt_re = prompt_re
43 self.initial_re = initial_re or prompt_re
52 self.initial_re = initial_re or prompt_re
44
53
45 def _strip(self, lines):
54 def _strip(self, lines):
46 return [self.prompt_re.sub('', l, count=1) for l in lines]
55 return [self.prompt_re.sub('', l, count=1) for l in lines]
47
56
48 def __call__(self, lines):
57 def __call__(self, lines):
49 if self.initial_re.match(lines[0]) or \
58 if self.initial_re.match(lines[0]) or \
50 (len(lines) > 1 and self.prompt_re.match(lines[1])):
59 (len(lines) > 1 and self.prompt_re.match(lines[1])):
51 return self._strip(lines)
60 return self._strip(lines)
52 return lines
61 return lines
53
62
54 classic_prompt = PromptStripper(
63 classic_prompt = PromptStripper(
55 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
64 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
56 initial_re=re.compile(r'^>>>( |$)')
65 initial_re=re.compile(r'^>>>( |$)')
57 )
66 )
58
67
59 ipython_prompt = PromptStripper(re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)'))
68 ipython_prompt = PromptStripper(re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)'))
60
69
61 def cell_magic(lines):
70 def cell_magic(lines):
62 if not lines[0].startswith('%%'):
71 if not lines[0].startswith('%%'):
63 return lines
72 return lines
64 if re.match('%%\w+\?', lines[0]):
73 if re.match('%%\w+\?', lines[0]):
65 # This case will be handled by help_end
74 # This case will be handled by help_end
66 return lines
75 return lines
67 magic_name, _, first_line = lines[0][2:-1].partition(' ')
76 magic_name, _, first_line = lines[0][2:-1].partition(' ')
68 body = ''.join(lines[1:])
77 body = ''.join(lines[1:])
69 return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
78 return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
70 % (magic_name, first_line, body)]
79 % (magic_name, first_line, body)]
71
80
72 # -----
81 # -----
73
82
74 def _find_assign_op(token_line):
83 def _find_assign_op(token_line):
75 # Find the first assignment in the line ('=' not inside brackets)
84 # Find the first assignment in the line ('=' not inside brackets)
76 # We don't try to support multiple special assignment (a = b = %foo)
85 # We don't try to support multiple special assignment (a = b = %foo)
77 paren_level = 0
86 paren_level = 0
78 for i, ti in enumerate(token_line):
87 for i, ti in enumerate(token_line):
79 s = ti.string
88 s = ti.string
80 if s == '=' and paren_level == 0:
89 if s == '=' and paren_level == 0:
81 return i
90 return i
82 if s in '([{':
91 if s in '([{':
83 paren_level += 1
92 paren_level += 1
84 elif s in ')]}':
93 elif s in ')]}':
85 paren_level -= 1
94 paren_level -= 1
86
95
87 def find_end_of_continued_line(lines, start_line: int):
96 def find_end_of_continued_line(lines, start_line: int):
88 """Find the last line of a line explicitly extended using backslashes.
97 """Find the last line of a line explicitly extended using backslashes.
89
98
90 Uses 0-indexed line numbers.
99 Uses 0-indexed line numbers.
91 """
100 """
92 end_line = start_line
101 end_line = start_line
93 while lines[end_line].endswith('\\\n'):
102 while lines[end_line].endswith('\\\n'):
94 end_line += 1
103 end_line += 1
95 if end_line >= len(lines):
104 if end_line >= len(lines):
96 break
105 break
97 return end_line
106 return end_line
98
107
99 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
108 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
100 """Assemble pieces of a continued line into a single line.
109 """Assemble pieces of a continued line into a single line.
101
110
102 Uses 0-indexed line numbers. *start* is (lineno, colno).
111 Uses 0-indexed line numbers. *start* is (lineno, colno).
103 """
112 """
104 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
113 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
105 return ' '.join([p[:-2] for p in parts[:-1]] # Strip backslash+newline
114 return ' '.join([p[:-2] for p in parts[:-1]] # Strip backslash+newline
106 + [parts[-1][:-1]]) # Strip newline from last line
115 + [parts[-1][:-1]]) # Strip newline from last line
107
116
108 class TokenTransformBase:
117 class TokenTransformBase:
109 # Lower numbers -> higher priority (for matches in the same location)
118 # Lower numbers -> higher priority (for matches in the same location)
110 priority = 10
119 priority = 10
111
120
112 def sortby(self):
121 def sortby(self):
113 return self.start_line, self.start_col, self.priority
122 return self.start_line, self.start_col, self.priority
114
123
115 def __init__(self, start):
124 def __init__(self, start):
116 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
125 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
117 self.start_col = start[1]
126 self.start_col = start[1]
118
127
119 def transform(self, lines: List[str]):
128 def transform(self, lines: List[str]):
120 raise NotImplementedError
129 raise NotImplementedError
121
130
122 class MagicAssign(TokenTransformBase):
131 class MagicAssign(TokenTransformBase):
123 @classmethod
132 @classmethod
124 def find(cls, tokens_by_line):
133 def find(cls, tokens_by_line):
125 """Find the first magic assignment (a = %foo) in the cell.
134 """Find the first magic assignment (a = %foo) in the cell.
126
135
127 Returns (line, column) of the % if found, or None. *line* is 1-indexed.
136 Returns (line, column) of the % if found, or None. *line* is 1-indexed.
128 """
137 """
129 for line in tokens_by_line:
138 for line in tokens_by_line:
130 assign_ix = _find_assign_op(line)
139 assign_ix = _find_assign_op(line)
131 if (assign_ix is not None) \
140 if (assign_ix is not None) \
132 and (len(line) >= assign_ix + 2) \
141 and (len(line) >= assign_ix + 2) \
133 and (line[assign_ix+1].string == '%') \
142 and (line[assign_ix+1].string == '%') \
134 and (line[assign_ix+2].type == tokenize2.NAME):
143 and (line[assign_ix+2].type == tokenize2.NAME):
135 return cls(line[assign_ix+1].start)
144 return cls(line[assign_ix+1].start)
136
145
137 def transform(self, lines: List[str]):
146 def transform(self, lines: List[str]):
138 """Transform a magic assignment found by find
147 """Transform a magic assignment found by find
139 """
148 """
140 start_line, start_col = self.start_line, self.start_col
149 start_line, start_col = self.start_line, self.start_col
141 lhs = lines[start_line][:start_col]
150 lhs = lines[start_line][:start_col]
142 end_line = find_end_of_continued_line(lines, start_line)
151 end_line = find_end_of_continued_line(lines, start_line)
143 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
152 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
144 assert rhs.startswith('%'), rhs
153 assert rhs.startswith('%'), rhs
145 magic_name, _, args = rhs[1:].partition(' ')
154 magic_name, _, args = rhs[1:].partition(' ')
146
155
147 lines_before = lines[:start_line]
156 lines_before = lines[:start_line]
148 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
157 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
149 new_line = lhs + call + '\n'
158 new_line = lhs + call + '\n'
150 lines_after = lines[end_line+1:]
159 lines_after = lines[end_line+1:]
151
160
152 return lines_before + [new_line] + lines_after
161 return lines_before + [new_line] + lines_after
153
162
154
163
155 class SystemAssign(TokenTransformBase):
164 class SystemAssign(TokenTransformBase):
156 @classmethod
165 @classmethod
157 def find(cls, tokens_by_line):
166 def find(cls, tokens_by_line):
158 """Find the first system assignment (a = !foo) in the cell.
167 """Find the first system assignment (a = !foo) in the cell.
159
168
160 Returns (line, column) of the ! if found, or None. *line* is 1-indexed.
169 Returns (line, column) of the ! if found, or None. *line* is 1-indexed.
161 """
170 """
162 for line in tokens_by_line:
171 for line in tokens_by_line:
163 assign_ix = _find_assign_op(line)
172 assign_ix = _find_assign_op(line)
164 if (assign_ix is not None) \
173 if (assign_ix is not None) \
165 and (len(line) >= assign_ix + 2) \
174 and (len(line) >= assign_ix + 2) \
166 and (line[assign_ix + 1].type == tokenize2.ERRORTOKEN):
175 and (line[assign_ix + 1].type == tokenize2.ERRORTOKEN):
167 ix = assign_ix + 1
176 ix = assign_ix + 1
168
177
169 while ix < len(line) and line[ix].type == tokenize2.ERRORTOKEN:
178 while ix < len(line) and line[ix].type == tokenize2.ERRORTOKEN:
170 if line[ix].string == '!':
179 if line[ix].string == '!':
171 return cls(line[ix].start)
180 return cls(line[ix].start)
172 elif not line[ix].string.isspace():
181 elif not line[ix].string.isspace():
173 break
182 break
174 ix += 1
183 ix += 1
175
184
176 def transform(self, lines: List[str]):
185 def transform(self, lines: List[str]):
177 """Transform a system assignment found by find
186 """Transform a system assignment found by find
178 """
187 """
179 start_line, start_col = self.start_line, self.start_col
188 start_line, start_col = self.start_line, self.start_col
180
189
181 lhs = lines[start_line][:start_col]
190 lhs = lines[start_line][:start_col]
182 end_line = find_end_of_continued_line(lines, start_line)
191 end_line = find_end_of_continued_line(lines, start_line)
183 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
192 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
184 assert rhs.startswith('!'), rhs
193 assert rhs.startswith('!'), rhs
185 cmd = rhs[1:]
194 cmd = rhs[1:]
186
195
187 lines_before = lines[:start_line]
196 lines_before = lines[:start_line]
188 call = "get_ipython().getoutput({!r})".format(cmd)
197 call = "get_ipython().getoutput({!r})".format(cmd)
189 new_line = lhs + call + '\n'
198 new_line = lhs + call + '\n'
190 lines_after = lines[end_line + 1:]
199 lines_after = lines[end_line + 1:]
191
200
192 return lines_before + [new_line] + lines_after
201 return lines_before + [new_line] + lines_after
193
202
194 # The escape sequences that define the syntax transformations IPython will
203 # The escape sequences that define the syntax transformations IPython will
195 # apply to user input. These can NOT be just changed here: many regular
204 # apply to user input. These can NOT be just changed here: many regular
196 # expressions and other parts of the code may use their hardcoded values, and
205 # expressions and other parts of the code may use their hardcoded values, and
197 # for all intents and purposes they constitute the 'IPython syntax', so they
206 # for all intents and purposes they constitute the 'IPython syntax', so they
198 # should be considered fixed.
207 # should be considered fixed.
199
208
200 ESC_SHELL = '!' # Send line to underlying system shell
209 ESC_SHELL = '!' # Send line to underlying system shell
201 ESC_SH_CAP = '!!' # Send line to system shell and capture output
210 ESC_SH_CAP = '!!' # Send line to system shell and capture output
202 ESC_HELP = '?' # Find information about object
211 ESC_HELP = '?' # Find information about object
203 ESC_HELP2 = '??' # Find extra-detailed information about object
212 ESC_HELP2 = '??' # Find extra-detailed information about object
204 ESC_MAGIC = '%' # Call magic function
213 ESC_MAGIC = '%' # Call magic function
205 ESC_MAGIC2 = '%%' # Call cell-magic function
214 ESC_MAGIC2 = '%%' # Call cell-magic function
206 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
215 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
207 ESC_QUOTE2 = ';' # Quote all args as a single string, call
216 ESC_QUOTE2 = ';' # Quote all args as a single string, call
208 ESC_PAREN = '/' # Call first argument with rest of line as arguments
217 ESC_PAREN = '/' # Call first argument with rest of line as arguments
209
218
210 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
219 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
211 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
220 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
212
221
213 def _make_help_call(target, esc, next_input=None):
222 def _make_help_call(target, esc, next_input=None):
214 """Prepares a pinfo(2)/psearch call from a target name and the escape
223 """Prepares a pinfo(2)/psearch call from a target name and the escape
215 (i.e. ? or ??)"""
224 (i.e. ? or ??)"""
216 method = 'pinfo2' if esc == '??' \
225 method = 'pinfo2' if esc == '??' \
217 else 'psearch' if '*' in target \
226 else 'psearch' if '*' in target \
218 else 'pinfo'
227 else 'pinfo'
219 arg = " ".join([method, target])
228 arg = " ".join([method, target])
220 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
229 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
221 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
230 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
222 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
231 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
223 if next_input is None:
232 if next_input is None:
224 return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s)
233 return 'get_ipython().run_line_magic(%r, %r)' % (t_magic_name, t_magic_arg_s)
225 else:
234 else:
226 return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
235 return 'get_ipython().set_next_input(%r);get_ipython().run_line_magic(%r, %r)' % \
227 (next_input, t_magic_name, t_magic_arg_s)
236 (next_input, t_magic_name, t_magic_arg_s)
228
237
229 def _tr_help(content):
238 def _tr_help(content):
230 "Translate lines escaped with: ?"
239 "Translate lines escaped with: ?"
231 # A naked help line should just fire the intro help screen
240 # A naked help line should just fire the intro help screen
232 if not content:
241 if not content:
233 return 'get_ipython().show_usage()'
242 return 'get_ipython().show_usage()'
234
243
235 return _make_help_call(content, '?')
244 return _make_help_call(content, '?')
236
245
237 def _tr_help2(content):
246 def _tr_help2(content):
238 "Translate lines escaped with: ??"
247 "Translate lines escaped with: ??"
239 # A naked help line should just fire the intro help screen
248 # A naked help line should just fire the intro help screen
240 if not content:
249 if not content:
241 return 'get_ipython().show_usage()'
250 return 'get_ipython().show_usage()'
242
251
243 return _make_help_call(content, '??')
252 return _make_help_call(content, '??')
244
253
245 def _tr_magic(content):
254 def _tr_magic(content):
246 "Translate lines escaped with: %"
255 "Translate lines escaped with: %"
247 name, _, args = content.partition(' ')
256 name, _, args = content.partition(' ')
248 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
257 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
249
258
250 def _tr_quote(content):
259 def _tr_quote(content):
251 "Translate lines escaped with: ,"
260 "Translate lines escaped with: ,"
252 name, _, args = content.partition(' ')
261 name, _, args = content.partition(' ')
253 return '%s("%s")' % (name, '", "'.join(args.split()) )
262 return '%s("%s")' % (name, '", "'.join(args.split()) )
254
263
255 def _tr_quote2(content):
264 def _tr_quote2(content):
256 "Translate lines escaped with: ;"
265 "Translate lines escaped with: ;"
257 name, _, args = content.partition(' ')
266 name, _, args = content.partition(' ')
258 return '%s("%s")' % (name, args)
267 return '%s("%s")' % (name, args)
259
268
260 def _tr_paren(content):
269 def _tr_paren(content):
261 "Translate lines escaped with: /"
270 "Translate lines escaped with: /"
262 name, _, args = content.partition(' ')
271 name, _, args = content.partition(' ')
263 return '%s(%s)' % (name, ", ".join(args.split()))
272 return '%s(%s)' % (name, ", ".join(args.split()))
264
273
265 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
274 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
266 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
275 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
267 ESC_HELP : _tr_help,
276 ESC_HELP : _tr_help,
268 ESC_HELP2 : _tr_help2,
277 ESC_HELP2 : _tr_help2,
269 ESC_MAGIC : _tr_magic,
278 ESC_MAGIC : _tr_magic,
270 ESC_QUOTE : _tr_quote,
279 ESC_QUOTE : _tr_quote,
271 ESC_QUOTE2 : _tr_quote2,
280 ESC_QUOTE2 : _tr_quote2,
272 ESC_PAREN : _tr_paren }
281 ESC_PAREN : _tr_paren }
273
282
274 class EscapedCommand(TokenTransformBase):
283 class EscapedCommand(TokenTransformBase):
275 @classmethod
284 @classmethod
276 def find(cls, tokens_by_line):
285 def find(cls, tokens_by_line):
277 """Find the first escaped command (%foo, !foo, etc.) in the cell.
286 """Find the first escaped command (%foo, !foo, etc.) in the cell.
278
287
279 Returns (line, column) of the escape if found, or None. *line* is 1-indexed.
288 Returns (line, column) of the escape if found, or None. *line* is 1-indexed.
280 """
289 """
281 for line in tokens_by_line:
290 for line in tokens_by_line:
282 ix = 0
291 ix = 0
283 while line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
292 while line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
284 ix += 1
293 ix += 1
285 if line[ix].string in ESCAPE_SINGLES:
294 if line[ix].string in ESCAPE_SINGLES:
286 return cls(line[ix].start)
295 return cls(line[ix].start)
287
296
288 def transform(self, lines):
297 def transform(self, lines):
289 start_line, start_col = self.start_line, self.start_col
298 start_line, start_col = self.start_line, self.start_col
290
299
291 indent = lines[start_line][:start_col]
300 indent = lines[start_line][:start_col]
292 end_line = find_end_of_continued_line(lines, start_line)
301 end_line = find_end_of_continued_line(lines, start_line)
293 line = assemble_continued_line(lines, (start_line, start_col), end_line)
302 line = assemble_continued_line(lines, (start_line, start_col), end_line)
294
303
295 if line[:2] in ESCAPE_DOUBLES:
304 if line[:2] in ESCAPE_DOUBLES:
296 escape, content = line[:2], line[2:]
305 escape, content = line[:2], line[2:]
297 else:
306 else:
298 escape, content = line[:1], line[1:]
307 escape, content = line[:1], line[1:]
299 call = tr[escape](content)
308 call = tr[escape](content)
300
309
301 lines_before = lines[:start_line]
310 lines_before = lines[:start_line]
302 new_line = indent + call + '\n'
311 new_line = indent + call + '\n'
303 lines_after = lines[end_line + 1:]
312 lines_after = lines[end_line + 1:]
304
313
305 return lines_before + [new_line] + lines_after
314 return lines_before + [new_line] + lines_after
306
315
307 _help_end_re = re.compile(r"""(%{0,2}
316 _help_end_re = re.compile(r"""(%{0,2}
308 [a-zA-Z_*][\w*]* # Variable name
317 [a-zA-Z_*][\w*]* # Variable name
309 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
318 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
310 )
319 )
311 (\?\??)$ # ? or ??
320 (\?\??)$ # ? or ??
312 """,
321 """,
313 re.VERBOSE)
322 re.VERBOSE)
314
323
315 class HelpEnd(TokenTransformBase):
324 class HelpEnd(TokenTransformBase):
316 # This needs to be higher priority (lower number) than EscapedCommand so
325 # This needs to be higher priority (lower number) than EscapedCommand so
317 # that inspecting magics (%foo?) works.
326 # that inspecting magics (%foo?) works.
318 priority = 5
327 priority = 5
319
328
320 def __init__(self, start, q_locn):
329 def __init__(self, start, q_locn):
321 super().__init__(start)
330 super().__init__(start)
322 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
331 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
323 self.q_col = q_locn[1]
332 self.q_col = q_locn[1]
324
333
325 @classmethod
334 @classmethod
326 def find(cls, tokens_by_line):
335 def find(cls, tokens_by_line):
327 for line in tokens_by_line:
336 for line in tokens_by_line:
328 # Last token is NEWLINE; look at last but one
337 # Last token is NEWLINE; look at last but one
329 if len(line) > 2 and line[-2].string == '?':
338 if len(line) > 2 and line[-2].string == '?':
330 # Find the first token that's not INDENT/DEDENT
339 # Find the first token that's not INDENT/DEDENT
331 ix = 0
340 ix = 0
332 while line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
341 while line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
333 ix += 1
342 ix += 1
334 return cls(line[ix].start, line[-2].start)
343 return cls(line[ix].start, line[-2].start)
335
344
336 def transform(self, lines):
345 def transform(self, lines):
337 piece = ''.join(lines[self.start_line:self.q_line+1])
346 piece = ''.join(lines[self.start_line:self.q_line+1])
338 indent, content = piece[:self.start_col], piece[self.start_col:]
347 indent, content = piece[:self.start_col], piece[self.start_col:]
339 lines_before = lines[:self.start_line]
348 lines_before = lines[:self.start_line]
340 lines_after = lines[self.q_line + 1:]
349 lines_after = lines[self.q_line + 1:]
341
350
342 m = _help_end_re.search(content)
351 m = _help_end_re.search(content)
343 assert m is not None, content
352 assert m is not None, content
344 target = m.group(1)
353 target = m.group(1)
345 esc = m.group(3)
354 esc = m.group(3)
346
355
347 # If we're mid-command, put it back on the next prompt for the user.
356 # If we're mid-command, put it back on the next prompt for the user.
348 next_input = None
357 next_input = None
349 if (not lines_before) and (not lines_after) \
358 if (not lines_before) and (not lines_after) \
350 and content.strip() != m.group(0):
359 and content.strip() != m.group(0):
351 next_input = content.rstrip('?\n')
360 next_input = content.rstrip('?\n')
352
361
353 call = _make_help_call(target, esc, next_input=next_input)
362 call = _make_help_call(target, esc, next_input=next_input)
354 new_line = indent + call + '\n'
363 new_line = indent + call + '\n'
355
364
356 return lines_before + [new_line] + lines_after
365 return lines_before + [new_line] + lines_after
357
366
358 def make_tokens_by_line(lines):
367 def make_tokens_by_line(lines):
359 tokens_by_line = [[]]
368 tokens_by_line = [[]]
360 for token in generate_tokens(iter(lines).__next__):
369 for token in generate_tokens(iter(lines).__next__):
361 tokens_by_line[-1].append(token)
370 tokens_by_line[-1].append(token)
362 if token.type == tokenize2.NEWLINE:
371 if token.type == tokenize2.NEWLINE:
363 tokens_by_line.append([])
372 tokens_by_line.append([])
364
373
365 return tokens_by_line
374 return tokens_by_line
366
375
367 def show_linewise_tokens(s: str):
376 def show_linewise_tokens(s: str):
368 """For investigation"""
377 """For investigation"""
369 if not s.endswith('\n'):
378 if not s.endswith('\n'):
370 s += '\n'
379 s += '\n'
371 lines = s.splitlines(keepends=True)
380 lines = s.splitlines(keepends=True)
372 for line in make_tokens_by_line(lines):
381 for line in make_tokens_by_line(lines):
373 print("Line -------")
382 print("Line -------")
374 for tokinfo in line:
383 for tokinfo in line:
375 print(" ", tokinfo)
384 print(" ", tokinfo)
376
385
377 class TransformerManager:
386 class TransformerManager:
378 def __init__(self):
387 def __init__(self):
379 self.cleanup_transforms = [
388 self.cleanup_transforms = [
380 leading_indent,
389 leading_indent,
381 classic_prompt,
390 classic_prompt,
382 ipython_prompt,
391 ipython_prompt,
383 ]
392 ]
384 self.line_transforms = [
393 self.line_transforms = [
385 cell_magic,
394 cell_magic,
386 ]
395 ]
387 self.token_transformers = [
396 self.token_transformers = [
388 MagicAssign,
397 MagicAssign,
389 SystemAssign,
398 SystemAssign,
390 EscapedCommand,
399 EscapedCommand,
391 HelpEnd,
400 HelpEnd,
392 ]
401 ]
393
402
394 def do_one_token_transform(self, lines):
403 def do_one_token_transform(self, lines):
395 """Find and run the transform earliest in the code.
404 """Find and run the transform earliest in the code.
396
405
397 Returns (changed, lines).
406 Returns (changed, lines).
398
407
399 This method is called repeatedly until changed is False, indicating
408 This method is called repeatedly until changed is False, indicating
400 that all available transformations are complete.
409 that all available transformations are complete.
401
410
402 The tokens following IPython special syntax might not be valid, so
411 The tokens following IPython special syntax might not be valid, so
403 the transformed code is retokenised every time to identify the next
412 the transformed code is retokenised every time to identify the next
404 piece of special syntax. Hopefully long code cells are mostly valid
413 piece of special syntax. Hopefully long code cells are mostly valid
405 Python, not using lots of IPython special syntax, so this shouldn't be
414 Python, not using lots of IPython special syntax, so this shouldn't be
406 a performance issue.
415 a performance issue.
407 """
416 """
408 tokens_by_line = make_tokens_by_line(lines)
417 tokens_by_line = make_tokens_by_line(lines)
409 candidates = []
418 candidates = []
410 for transformer_cls in self.token_transformers:
419 for transformer_cls in self.token_transformers:
411 transformer = transformer_cls.find(tokens_by_line)
420 transformer = transformer_cls.find(tokens_by_line)
412 if transformer:
421 if transformer:
413 candidates.append(transformer)
422 candidates.append(transformer)
414
423
415 if not candidates:
424 if not candidates:
416 # Nothing to transform
425 # Nothing to transform
417 return False, lines
426 return False, lines
418
427
419 transformer = min(candidates, key=TokenTransformBase.sortby)
428 transformer = min(candidates, key=TokenTransformBase.sortby)
420 return True, transformer.transform(lines)
429 return True, transformer.transform(lines)
421
430
422 def do_token_transforms(self, lines):
431 def do_token_transforms(self, lines):
423 while True:
432 while True:
424 changed, lines = self.do_one_token_transform(lines)
433 changed, lines = self.do_one_token_transform(lines)
425 if not changed:
434 if not changed:
426 return lines
435 return lines
427
436
428 def transform_cell(self, cell: str):
437 def transform_cell(self, cell: str):
429 if not cell.endswith('\n'):
438 if not cell.endswith('\n'):
430 cell += '\n' # Ensure the cell has a trailing newline
439 cell += '\n' # Ensure the cell has a trailing newline
431 lines = cell.splitlines(keepends=True)
440 lines = cell.splitlines(keepends=True)
432 for transform in self.cleanup_transforms + self.line_transforms:
441 for transform in self.cleanup_transforms + self.line_transforms:
433 #print(transform, lines)
442 #print(transform, lines)
434 lines = transform(lines)
443 lines = transform(lines)
435
444
436 lines = self.do_token_transforms(lines)
445 lines = self.do_token_transforms(lines)
437 return ''.join(lines)
446 return ''.join(lines)
438
447
439 def check_complete(self, cell: str):
448 def check_complete(self, cell: str):
440 """Return whether a block of code is ready to execute, or should be continued
449 """Return whether a block of code is ready to execute, or should be continued
441
450
442 Parameters
451 Parameters
443 ----------
452 ----------
444 source : string
453 source : string
445 Python input code, which can be multiline.
454 Python input code, which can be multiline.
446
455
447 Returns
456 Returns
448 -------
457 -------
449 status : str
458 status : str
450 One of 'complete', 'incomplete', or 'invalid' if source is not a
459 One of 'complete', 'incomplete', or 'invalid' if source is not a
451 prefix of valid code.
460 prefix of valid code.
452 indent_spaces : int or None
461 indent_spaces : int or None
453 The number of spaces by which to indent the next line of code. If
462 The number of spaces by which to indent the next line of code. If
454 status is not 'incomplete', this is None.
463 status is not 'incomplete', this is None.
455 """
464 """
456 if not cell.endswith('\n'):
465 if not cell.endswith('\n'):
457 cell += '\n' # Ensure the cell has a trailing newline
466 cell += '\n' # Ensure the cell has a trailing newline
458 lines = cell.splitlines(keepends=True)
467 lines = cell.splitlines(keepends=True)
459 if lines[-1][:-1].endswith('\\'):
468 if lines[-1][:-1].endswith('\\'):
460 # Explicit backslash continuation
469 # Explicit backslash continuation
461 return 'incomplete', find_last_indent(lines)
470 return 'incomplete', find_last_indent(lines)
462
471
463 try:
472 try:
464 for transform in self.cleanup_transforms:
473 for transform in self.cleanup_transforms:
465 lines = transform(lines)
474 lines = transform(lines)
466 except SyntaxError:
475 except SyntaxError:
467 return 'invalid', None
476 return 'invalid', None
468
477
469 if lines[0].startswith('%%'):
478 if lines[0].startswith('%%'):
470 # Special case for cell magics - completion marked by blank line
479 # Special case for cell magics - completion marked by blank line
471 if lines[-1].strip():
480 if lines[-1].strip():
472 return 'incomplete', find_last_indent(lines)
481 return 'incomplete', find_last_indent(lines)
473 else:
482 else:
474 return 'complete', None
483 return 'complete', None
475
484
476 try:
485 try:
477 for transform in self.line_transforms:
486 for transform in self.line_transforms:
478 lines = transform(lines)
487 lines = transform(lines)
479 lines = self.do_token_transforms(lines)
488 lines = self.do_token_transforms(lines)
480 except SyntaxError:
489 except SyntaxError:
481 return 'invalid', None
490 return 'invalid', None
482
491
483 tokens_by_line = make_tokens_by_line(lines)
492 tokens_by_line = make_tokens_by_line(lines)
484 if tokens_by_line[-1][-1].type != tokenize2.ENDMARKER:
493 if tokens_by_line[-1][-1].type != tokenize2.ENDMARKER:
485 # We're in a multiline string or expression
494 # We're in a multiline string or expression
486 return 'incomplete', find_last_indent(lines)
495 return 'incomplete', find_last_indent(lines)
487
496
488 # Find the last token on the previous line that's not NEWLINE or COMMENT
497 # Find the last token on the previous line that's not NEWLINE or COMMENT
489 toks_last_line = tokens_by_line[-2]
498 toks_last_line = tokens_by_line[-2]
490 ix = len(toks_last_line) - 1
499 ix = len(toks_last_line) - 1
491 while ix >= 0 and toks_last_line[ix].type in {tokenize2.NEWLINE,
500 while ix >= 0 and toks_last_line[ix].type in {tokenize2.NEWLINE,
492 tokenize2.COMMENT}:
501 tokenize2.COMMENT}:
493 ix -= 1
502 ix -= 1
494
503
495 if toks_last_line[ix].string == ':':
504 if toks_last_line[ix].string == ':':
496 # The last line starts a block (e.g. 'if foo:')
505 # The last line starts a block (e.g. 'if foo:')
497 ix = 0
506 ix = 0
498 while toks_last_line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
507 while toks_last_line[ix].type in {tokenize2.INDENT, tokenize2.DEDENT}:
499 ix += 1
508 ix += 1
500 indent = toks_last_line[ix].start[1]
509 indent = toks_last_line[ix].start[1]
501 return 'incomplete', indent + 4
510 return 'incomplete', indent + 4
502
511
503 # If there's a blank line at the end, assume we're ready to execute.
512 # If there's a blank line at the end, assume we're ready to execute.
504 if not lines[-1].strip():
513 if not lines[-1].strip():
505 return 'complete', None
514 return 'complete', None
506
515
507 # At this point, our checks think the code is complete (or invalid).
516 # At this point, our checks think the code is complete (or invalid).
508 # We'll use codeop.compile_command to check this with the real parser.
517 # We'll use codeop.compile_command to check this with the real parser.
509
518
510 try:
519 try:
511 res = compile_command(''.join(lines), symbol='exec')
520 res = compile_command(''.join(lines), symbol='exec')
512 except (SyntaxError, OverflowError, ValueError, TypeError,
521 except (SyntaxError, OverflowError, ValueError, TypeError,
513 MemoryError, SyntaxWarning):
522 MemoryError, SyntaxWarning):
514 return 'invalid', None
523 return 'invalid', None
515 else:
524 else:
516 if res is None:
525 if res is None:
517 return 'incomplete', find_last_indent(lines)
526 return 'incomplete', find_last_indent(lines)
518 return 'complete', None
527 return 'complete', None
519
528
520
529
521 def find_last_indent(lines):
530 def find_last_indent(lines):
522 m = _indent_re.match(lines[-1])
531 m = _indent_re.match(lines[-1])
523 if not m:
532 if not m:
524 return 0
533 return 0
525 return len(m.group(0).replace('\t', ' '*4))
534 return len(m.group(0).replace('\t', ' '*4))
General Comments 0
You need to be logged in to leave comments. Login now