##// END OF EJS Templates
Well, since you ask me for Python 2.7 support...
Thomas Kluyver -
Show More
@@ -1,554 +1,555 b''
1 """Input transformer classes to support IPython special syntax.
1 """Input transformer classes to support IPython special syntax.
2
2
3 This includes the machinery to recognise and transform ``%magic`` commands,
3 This includes the machinery to recognise and transform ``%magic`` commands,
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
5 """
5 """
6 import abc
6 import abc
7 import functools
7 import functools
8 import re
8 import re
9
9
10 from IPython.core.splitinput import LineInfo
10 from IPython.core.splitinput import LineInfo
11 from IPython.utils import tokenize2
11 from IPython.utils import tokenize2
12 from IPython.utils.openpy import cookie_comment_re
12 from IPython.utils.openpy import cookie_comment_re
13 from IPython.utils.py3compat import with_metaclass, PY3
13 from IPython.utils.py3compat import with_metaclass, PY3
14 from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError
14 from IPython.utils.tokenize2 import generate_tokens, untokenize, TokenError
15
15
16 if PY3:
16 if PY3:
17 from io import StringIO
17 from io import StringIO
18 else:
18 else:
19 from StringIO import StringIO
19 from StringIO import StringIO
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Globals
22 # Globals
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 # The escape sequences that define the syntax transformations IPython will
25 # The escape sequences that define the syntax transformations IPython will
26 # apply to user input. These can NOT be just changed here: many regular
26 # apply to user input. These can NOT be just changed here: many regular
27 # expressions and other parts of the code may use their hardcoded values, and
27 # expressions and other parts of the code may use their hardcoded values, and
28 # for all intents and purposes they constitute the 'IPython syntax', so they
28 # for all intents and purposes they constitute the 'IPython syntax', so they
29 # should be considered fixed.
29 # should be considered fixed.
30
30
31 ESC_SHELL = '!' # Send line to underlying system shell
31 ESC_SHELL = '!' # Send line to underlying system shell
32 ESC_SH_CAP = '!!' # Send line to system shell and capture output
32 ESC_SH_CAP = '!!' # Send line to system shell and capture output
33 ESC_HELP = '?' # Find information about object
33 ESC_HELP = '?' # Find information about object
34 ESC_HELP2 = '??' # Find extra-detailed information about object
34 ESC_HELP2 = '??' # Find extra-detailed information about object
35 ESC_MAGIC = '%' # Call magic function
35 ESC_MAGIC = '%' # Call magic function
36 ESC_MAGIC2 = '%%' # Call cell-magic function
36 ESC_MAGIC2 = '%%' # Call cell-magic function
37 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
37 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
38 ESC_QUOTE2 = ';' # Quote all args as a single string, call
38 ESC_QUOTE2 = ';' # Quote all args as a single string, call
39 ESC_PAREN = '/' # Call first argument with rest of line as arguments
39 ESC_PAREN = '/' # Call first argument with rest of line as arguments
40
40
41 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
41 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
42 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
42 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
43 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
43 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
44
44
45
45
46 class InputTransformer(with_metaclass(abc.ABCMeta, object)):
46 class InputTransformer(with_metaclass(abc.ABCMeta, object)):
47 """Abstract base class for line-based input transformers."""
47 """Abstract base class for line-based input transformers."""
48
48
49 @abc.abstractmethod
49 @abc.abstractmethod
50 def push(self, line):
50 def push(self, line):
51 """Send a line of input to the transformer, returning the transformed
51 """Send a line of input to the transformer, returning the transformed
52 input or None if the transformer is waiting for more input.
52 input or None if the transformer is waiting for more input.
53
53
54 Must be overridden by subclasses.
54 Must be overridden by subclasses.
55
55
56 Implementations may raise ``SyntaxError`` if the input is invalid. No
56 Implementations may raise ``SyntaxError`` if the input is invalid. No
57 other exceptions may be raised.
57 other exceptions may be raised.
58 """
58 """
59 pass
59 pass
60
60
61 @abc.abstractmethod
61 @abc.abstractmethod
62 def reset(self):
62 def reset(self):
63 """Return, transformed any lines that the transformer has accumulated,
63 """Return, transformed any lines that the transformer has accumulated,
64 and reset its internal state.
64 and reset its internal state.
65
65
66 Must be overridden by subclasses.
66 Must be overridden by subclasses.
67 """
67 """
68 pass
68 pass
69
69
70 @classmethod
70 @classmethod
71 def wrap(cls, func):
71 def wrap(cls, func):
72 """Can be used by subclasses as a decorator, to return a factory that
72 """Can be used by subclasses as a decorator, to return a factory that
73 will allow instantiation with the decorated object.
73 will allow instantiation with the decorated object.
74 """
74 """
75 @functools.wraps(func)
75 @functools.wraps(func)
76 def transformer_factory(**kwargs):
76 def transformer_factory(**kwargs):
77 return cls(func, **kwargs)
77 return cls(func, **kwargs)
78
78
79 return transformer_factory
79 return transformer_factory
80
80
81 class StatelessInputTransformer(InputTransformer):
81 class StatelessInputTransformer(InputTransformer):
82 """Wrapper for a stateless input transformer implemented as a function."""
82 """Wrapper for a stateless input transformer implemented as a function."""
83 def __init__(self, func):
83 def __init__(self, func):
84 self.func = func
84 self.func = func
85
85
86 def __repr__(self):
86 def __repr__(self):
87 return "StatelessInputTransformer(func={0!r})".format(self.func)
87 return "StatelessInputTransformer(func={0!r})".format(self.func)
88
88
89 def push(self, line):
89 def push(self, line):
90 """Send a line of input to the transformer, returning the
90 """Send a line of input to the transformer, returning the
91 transformed input."""
91 transformed input."""
92 return self.func(line)
92 return self.func(line)
93
93
94 def reset(self):
94 def reset(self):
95 """No-op - exists for compatibility."""
95 """No-op - exists for compatibility."""
96 pass
96 pass
97
97
98 class CoroutineInputTransformer(InputTransformer):
98 class CoroutineInputTransformer(InputTransformer):
99 """Wrapper for an input transformer implemented as a coroutine."""
99 """Wrapper for an input transformer implemented as a coroutine."""
100 def __init__(self, coro, **kwargs):
100 def __init__(self, coro, **kwargs):
101 # Prime it
101 # Prime it
102 self.coro = coro(**kwargs)
102 self.coro = coro(**kwargs)
103 next(self.coro)
103 next(self.coro)
104
104
105 def __repr__(self):
105 def __repr__(self):
106 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
106 return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
107
107
108 def push(self, line):
108 def push(self, line):
109 """Send a line of input to the transformer, returning the
109 """Send a line of input to the transformer, returning the
110 transformed input or None if the transformer is waiting for more
110 transformed input or None if the transformer is waiting for more
111 input.
111 input.
112 """
112 """
113 return self.coro.send(line)
113 return self.coro.send(line)
114
114
115 def reset(self):
115 def reset(self):
116 """Return, transformed any lines that the transformer has
116 """Return, transformed any lines that the transformer has
117 accumulated, and reset its internal state.
117 accumulated, and reset its internal state.
118 """
118 """
119 return self.coro.send(None)
119 return self.coro.send(None)
120
120
121 class TokenInputTransformer(InputTransformer):
121 class TokenInputTransformer(InputTransformer):
122 """Wrapper for a token-based input transformer.
122 """Wrapper for a token-based input transformer.
123
123
124 func should accept a list of tokens (5-tuples, see tokenize docs), and
124 func should accept a list of tokens (5-tuples, see tokenize docs), and
125 return an iterable which can be passed to tokenize.untokenize().
125 return an iterable which can be passed to tokenize.untokenize().
126 """
126 """
127 def __init__(self, func):
127 def __init__(self, func):
128 self.func = func
128 self.func = func
129 self.buf = []
129 self.buf = []
130 self.reset_tokenizer()
130 self.reset_tokenizer()
131
131
132 def reset_tokenizer(self):
132 def reset_tokenizer(self):
133 it = iter(self.buf)
133 it = iter(self.buf)
134 self.tokenizer = generate_tokens(it.__next__)
134 nxt = it.__next__ if PY3 else it.next
135 self.tokenizer = generate_tokens(nxt)
135
136
136 def push(self, line):
137 def push(self, line):
137 self.buf.append(line + '\n')
138 self.buf.append(line + '\n')
138 if all(l.isspace() for l in self.buf):
139 if all(l.isspace() for l in self.buf):
139 return self.reset()
140 return self.reset()
140
141
141 tokens = []
142 tokens = []
142 stop_at_NL = False
143 stop_at_NL = False
143 try:
144 try:
144 for intok in self.tokenizer:
145 for intok in self.tokenizer:
145 tokens.append(intok)
146 tokens.append(intok)
146 t = intok[0]
147 t = intok[0]
147 if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL):
148 if t == tokenize2.NEWLINE or (stop_at_NL and t == tokenize2.NL):
148 # Stop before we try to pull a line we don't have yet
149 # Stop before we try to pull a line we don't have yet
149 break
150 break
150 elif t == tokenize2.ERRORTOKEN:
151 elif t == tokenize2.ERRORTOKEN:
151 stop_at_NL = True
152 stop_at_NL = True
152 except TokenError:
153 except TokenError:
153 # Multi-line statement - stop and try again with the next line
154 # Multi-line statement - stop and try again with the next line
154 self.reset_tokenizer()
155 self.reset_tokenizer()
155 return None
156 return None
156
157
157 return self.output(tokens)
158 return self.output(tokens)
158
159
159 def output(self, tokens):
160 def output(self, tokens):
160 self.buf.clear()
161 self.buf.clear()
161 self.reset_tokenizer()
162 self.reset_tokenizer()
162 return untokenize(self.func(tokens)).rstrip('\n')
163 return untokenize(self.func(tokens)).rstrip('\n')
163
164
164 def reset(self):
165 def reset(self):
165 l = ''.join(self.buf)
166 l = ''.join(self.buf)
166 self.buf.clear()
167 self.buf.clear()
167 self.reset_tokenizer()
168 self.reset_tokenizer()
168 if l:
169 if l:
169 return l.rstrip('\n')
170 return l.rstrip('\n')
170
171
171 class assemble_python_lines(TokenInputTransformer):
172 class assemble_python_lines(TokenInputTransformer):
172 def __init__(self):
173 def __init__(self):
173 super(assemble_python_lines, self).__init__(None)
174 super(assemble_python_lines, self).__init__(None)
174
175
175 def output(self, tokens):
176 def output(self, tokens):
176 return self.reset()
177 return self.reset()
177
178
178 @CoroutineInputTransformer.wrap
179 @CoroutineInputTransformer.wrap
179 def assemble_logical_lines():
180 def assemble_logical_lines():
180 """Join lines following explicit line continuations (\)"""
181 """Join lines following explicit line continuations (\)"""
181 line = ''
182 line = ''
182 while True:
183 while True:
183 line = (yield line)
184 line = (yield line)
184 if not line or line.isspace():
185 if not line or line.isspace():
185 continue
186 continue
186
187
187 parts = []
188 parts = []
188 while line is not None:
189 while line is not None:
189 if line.endswith('\\') and (not has_comment(line)):
190 if line.endswith('\\') and (not has_comment(line)):
190 parts.append(line[:-1])
191 parts.append(line[:-1])
191 line = (yield None) # Get another line
192 line = (yield None) # Get another line
192 else:
193 else:
193 parts.append(line)
194 parts.append(line)
194 break
195 break
195
196
196 # Output
197 # Output
197 line = ''.join(parts)
198 line = ''.join(parts)
198
199
199 # Utilities
200 # Utilities
200 def _make_help_call(target, esc, lspace, next_input=None):
201 def _make_help_call(target, esc, lspace, next_input=None):
201 """Prepares a pinfo(2)/psearch call from a target name and the escape
202 """Prepares a pinfo(2)/psearch call from a target name and the escape
202 (i.e. ? or ??)"""
203 (i.e. ? or ??)"""
203 method = 'pinfo2' if esc == '??' \
204 method = 'pinfo2' if esc == '??' \
204 else 'psearch' if '*' in target \
205 else 'psearch' if '*' in target \
205 else 'pinfo'
206 else 'pinfo'
206 arg = " ".join([method, target])
207 arg = " ".join([method, target])
207 if next_input is None:
208 if next_input is None:
208 return '%sget_ipython().magic(%r)' % (lspace, arg)
209 return '%sget_ipython().magic(%r)' % (lspace, arg)
209 else:
210 else:
210 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
211 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
211 (lspace, next_input, arg)
212 (lspace, next_input, arg)
212
213
213 # These define the transformations for the different escape characters.
214 # These define the transformations for the different escape characters.
214 def _tr_system(line_info):
215 def _tr_system(line_info):
215 "Translate lines escaped with: !"
216 "Translate lines escaped with: !"
216 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
217 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
217 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
218 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
218
219
219 def _tr_system2(line_info):
220 def _tr_system2(line_info):
220 "Translate lines escaped with: !!"
221 "Translate lines escaped with: !!"
221 cmd = line_info.line.lstrip()[2:]
222 cmd = line_info.line.lstrip()[2:]
222 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
223 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
223
224
224 def _tr_help(line_info):
225 def _tr_help(line_info):
225 "Translate lines escaped with: ?/??"
226 "Translate lines escaped with: ?/??"
226 # A naked help line should just fire the intro help screen
227 # A naked help line should just fire the intro help screen
227 if not line_info.line[1:]:
228 if not line_info.line[1:]:
228 return 'get_ipython().show_usage()'
229 return 'get_ipython().show_usage()'
229
230
230 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
231 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
231
232
232 def _tr_magic(line_info):
233 def _tr_magic(line_info):
233 "Translate lines escaped with: %"
234 "Translate lines escaped with: %"
234 tpl = '%sget_ipython().magic(%r)'
235 tpl = '%sget_ipython().magic(%r)'
235 if line_info.line.startswith(ESC_MAGIC2):
236 if line_info.line.startswith(ESC_MAGIC2):
236 return line_info.line
237 return line_info.line
237 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
238 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
238 return tpl % (line_info.pre, cmd)
239 return tpl % (line_info.pre, cmd)
239
240
240 def _tr_quote(line_info):
241 def _tr_quote(line_info):
241 "Translate lines escaped with: ,"
242 "Translate lines escaped with: ,"
242 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
243 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
243 '", "'.join(line_info.the_rest.split()) )
244 '", "'.join(line_info.the_rest.split()) )
244
245
245 def _tr_quote2(line_info):
246 def _tr_quote2(line_info):
246 "Translate lines escaped with: ;"
247 "Translate lines escaped with: ;"
247 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
248 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
248 line_info.the_rest)
249 line_info.the_rest)
249
250
250 def _tr_paren(line_info):
251 def _tr_paren(line_info):
251 "Translate lines escaped with: /"
252 "Translate lines escaped with: /"
252 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
253 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
253 ", ".join(line_info.the_rest.split()))
254 ", ".join(line_info.the_rest.split()))
254
255
255 tr = { ESC_SHELL : _tr_system,
256 tr = { ESC_SHELL : _tr_system,
256 ESC_SH_CAP : _tr_system2,
257 ESC_SH_CAP : _tr_system2,
257 ESC_HELP : _tr_help,
258 ESC_HELP : _tr_help,
258 ESC_HELP2 : _tr_help,
259 ESC_HELP2 : _tr_help,
259 ESC_MAGIC : _tr_magic,
260 ESC_MAGIC : _tr_magic,
260 ESC_QUOTE : _tr_quote,
261 ESC_QUOTE : _tr_quote,
261 ESC_QUOTE2 : _tr_quote2,
262 ESC_QUOTE2 : _tr_quote2,
262 ESC_PAREN : _tr_paren }
263 ESC_PAREN : _tr_paren }
263
264
264 @StatelessInputTransformer.wrap
265 @StatelessInputTransformer.wrap
265 def escaped_commands(line):
266 def escaped_commands(line):
266 """Transform escaped commands - %magic, !system, ?help + various autocalls.
267 """Transform escaped commands - %magic, !system, ?help + various autocalls.
267 """
268 """
268 if not line or line.isspace():
269 if not line or line.isspace():
269 return line
270 return line
270 lineinf = LineInfo(line)
271 lineinf = LineInfo(line)
271 if lineinf.esc not in tr:
272 if lineinf.esc not in tr:
272 return line
273 return line
273
274
274 return tr[lineinf.esc](lineinf)
275 return tr[lineinf.esc](lineinf)
275
276
276 _initial_space_re = re.compile(r'\s*')
277 _initial_space_re = re.compile(r'\s*')
277
278
278 _help_end_re = re.compile(r"""(%{0,2}
279 _help_end_re = re.compile(r"""(%{0,2}
279 [a-zA-Z_*][\w*]* # Variable name
280 [a-zA-Z_*][\w*]* # Variable name
280 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
281 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
281 )
282 )
282 (\?\??)$ # ? or ??
283 (\?\??)$ # ? or ??
283 """,
284 """,
284 re.VERBOSE)
285 re.VERBOSE)
285
286
286 # Extra pseudotokens for multiline strings and data structures
287 # Extra pseudotokens for multiline strings and data structures
287 _MULTILINE_STRING = object()
288 _MULTILINE_STRING = object()
288 _MULTILINE_STRUCTURE = object()
289 _MULTILINE_STRUCTURE = object()
289
290
290 def _line_tokens(line):
291 def _line_tokens(line):
291 """Helper for has_comment and ends_in_comment_or_string."""
292 """Helper for has_comment and ends_in_comment_or_string."""
292 readline = StringIO(line).readline
293 readline = StringIO(line).readline
293 toktypes = set()
294 toktypes = set()
294 try:
295 try:
295 for t in generate_tokens(readline):
296 for t in generate_tokens(readline):
296 toktypes.add(t[0])
297 toktypes.add(t[0])
297 except TokenError as e:
298 except TokenError as e:
298 # There are only two cases where a TokenError is raised.
299 # There are only two cases where a TokenError is raised.
299 if 'multi-line string' in e.args[0]:
300 if 'multi-line string' in e.args[0]:
300 toktypes.add(_MULTILINE_STRING)
301 toktypes.add(_MULTILINE_STRING)
301 else:
302 else:
302 toktypes.add(_MULTILINE_STRUCTURE)
303 toktypes.add(_MULTILINE_STRUCTURE)
303 return toktypes
304 return toktypes
304
305
305 def has_comment(src):
306 def has_comment(src):
306 """Indicate whether an input line has (i.e. ends in, or is) a comment.
307 """Indicate whether an input line has (i.e. ends in, or is) a comment.
307
308
308 This uses tokenize, so it can distinguish comments from # inside strings.
309 This uses tokenize, so it can distinguish comments from # inside strings.
309
310
310 Parameters
311 Parameters
311 ----------
312 ----------
312 src : string
313 src : string
313 A single line input string.
314 A single line input string.
314
315
315 Returns
316 Returns
316 -------
317 -------
317 comment : bool
318 comment : bool
318 True if source has a comment.
319 True if source has a comment.
319 """
320 """
320 return (tokenize2.COMMENT in _line_tokens(src))
321 return (tokenize2.COMMENT in _line_tokens(src))
321
322
322 def ends_in_comment_or_string(src):
323 def ends_in_comment_or_string(src):
323 """Indicates whether or not an input line ends in a comment or within
324 """Indicates whether or not an input line ends in a comment or within
324 a multiline string.
325 a multiline string.
325
326
326 Parameters
327 Parameters
327 ----------
328 ----------
328 src : string
329 src : string
329 A single line input string.
330 A single line input string.
330
331
331 Returns
332 Returns
332 -------
333 -------
333 comment : bool
334 comment : bool
334 True if source ends in a comment or multiline string.
335 True if source ends in a comment or multiline string.
335 """
336 """
336 toktypes = _line_tokens(src)
337 toktypes = _line_tokens(src)
337 return (tokenize2.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
338 return (tokenize2.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
338
339
339
340
340 @StatelessInputTransformer.wrap
341 @StatelessInputTransformer.wrap
341 def help_end(line):
342 def help_end(line):
342 """Translate lines with ?/?? at the end"""
343 """Translate lines with ?/?? at the end"""
343 m = _help_end_re.search(line)
344 m = _help_end_re.search(line)
344 if m is None or ends_in_comment_or_string(line):
345 if m is None or ends_in_comment_or_string(line):
345 return line
346 return line
346 target = m.group(1)
347 target = m.group(1)
347 esc = m.group(3)
348 esc = m.group(3)
348 lspace = _initial_space_re.match(line).group(0)
349 lspace = _initial_space_re.match(line).group(0)
349
350
350 # If we're mid-command, put it back on the next prompt for the user.
351 # 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
352 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
352
353
353 return _make_help_call(target, esc, lspace, next_input)
354 return _make_help_call(target, esc, lspace, next_input)
354
355
355
356
356 @CoroutineInputTransformer.wrap
357 @CoroutineInputTransformer.wrap
357 def cellmagic(end_on_blank_line=False):
358 def cellmagic(end_on_blank_line=False):
358 """Captures & transforms cell magics.
359 """Captures & transforms cell magics.
359
360
360 After a cell magic is started, this stores up any lines it gets until it is
361 After a cell magic is started, this stores up any lines it gets until it is
361 reset (sent None).
362 reset (sent None).
362 """
363 """
363 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
364 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
364 cellmagic_help_re = re.compile('%%\w+\?')
365 cellmagic_help_re = re.compile('%%\w+\?')
365 line = ''
366 line = ''
366 while True:
367 while True:
367 line = (yield line)
368 line = (yield line)
368 # consume leading empty lines
369 # consume leading empty lines
369 while not line:
370 while not line:
370 line = (yield line)
371 line = (yield line)
371
372
372 if not line.startswith(ESC_MAGIC2):
373 if not line.startswith(ESC_MAGIC2):
373 # This isn't a cell magic, idle waiting for reset then start over
374 # This isn't a cell magic, idle waiting for reset then start over
374 while line is not None:
375 while line is not None:
375 line = (yield line)
376 line = (yield line)
376 continue
377 continue
377
378
378 if cellmagic_help_re.match(line):
379 if cellmagic_help_re.match(line):
379 # This case will be handled by help_end
380 # This case will be handled by help_end
380 continue
381 continue
381
382
382 first = line
383 first = line
383 body = []
384 body = []
384 line = (yield None)
385 line = (yield None)
385 while (line is not None) and \
386 while (line is not None) and \
386 ((line.strip() != '') or not end_on_blank_line):
387 ((line.strip() != '') or not end_on_blank_line):
387 body.append(line)
388 body.append(line)
388 line = (yield None)
389 line = (yield None)
389
390
390 # Output
391 # Output
391 magic_name, _, first = first.partition(' ')
392 magic_name, _, first = first.partition(' ')
392 magic_name = magic_name.lstrip(ESC_MAGIC2)
393 magic_name = magic_name.lstrip(ESC_MAGIC2)
393 line = tpl % (magic_name, first, u'\n'.join(body))
394 line = tpl % (magic_name, first, u'\n'.join(body))
394
395
395
396
396 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
397 def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
397 """Remove matching input prompts from a block of input.
398 """Remove matching input prompts from a block of input.
398
399
399 Parameters
400 Parameters
400 ----------
401 ----------
401 prompt_re : regular expression
402 prompt_re : regular expression
402 A regular expression matching any input prompt (including continuation)
403 A regular expression matching any input prompt (including continuation)
403 initial_re : regular expression, optional
404 initial_re : regular expression, optional
404 A regular expression matching only the initial prompt, but not continuation.
405 A regular expression matching only the initial prompt, but not continuation.
405 If no initial expression is given, prompt_re will be used everywhere.
406 If no initial expression is given, prompt_re will be used everywhere.
406 Used mainly for plain Python prompts, where the continuation prompt
407 Used mainly for plain Python prompts, where the continuation prompt
407 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
408 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
408
409
409 If initial_re and prompt_re differ,
410 If initial_re and prompt_re differ,
410 only initial_re will be tested against the first line.
411 only initial_re will be tested against the first line.
411 If any prompt is found on the first two lines,
412 If any prompt is found on the first two lines,
412 prompts will be stripped from the rest of the block.
413 prompts will be stripped from the rest of the block.
413 """
414 """
414 if initial_re is None:
415 if initial_re is None:
415 initial_re = prompt_re
416 initial_re = prompt_re
416 line = ''
417 line = ''
417 while True:
418 while True:
418 line = (yield line)
419 line = (yield line)
419
420
420 # First line of cell
421 # First line of cell
421 if line is None:
422 if line is None:
422 continue
423 continue
423 out, n1 = initial_re.subn('', line, count=1)
424 out, n1 = initial_re.subn('', line, count=1)
424 if turnoff_re and not n1:
425 if turnoff_re and not n1:
425 if turnoff_re.match(line):
426 if turnoff_re.match(line):
426 # We're in e.g. a cell magic; disable this transformer for
427 # We're in e.g. a cell magic; disable this transformer for
427 # the rest of the cell.
428 # the rest of the cell.
428 while line is not None:
429 while line is not None:
429 line = (yield line)
430 line = (yield line)
430 continue
431 continue
431
432
432 line = (yield out)
433 line = (yield out)
433
434
434 if line is None:
435 if line is None:
435 continue
436 continue
436 # check for any prompt on the second line of the cell,
437 # check for any prompt on the second line of the cell,
437 # because people often copy from just after the first prompt,
438 # because people often copy from just after the first prompt,
438 # so we might not see it in the first line.
439 # so we might not see it in the first line.
439 out, n2 = prompt_re.subn('', line, count=1)
440 out, n2 = prompt_re.subn('', line, count=1)
440 line = (yield out)
441 line = (yield out)
441
442
442 if n1 or n2:
443 if n1 or n2:
443 # Found a prompt in the first two lines - check for it in
444 # Found a prompt in the first two lines - check for it in
444 # the rest of the cell as well.
445 # the rest of the cell as well.
445 while line is not None:
446 while line is not None:
446 line = (yield prompt_re.sub('', line, count=1))
447 line = (yield prompt_re.sub('', line, count=1))
447
448
448 else:
449 else:
449 # Prompts not in input - wait for reset
450 # Prompts not in input - wait for reset
450 while line is not None:
451 while line is not None:
451 line = (yield line)
452 line = (yield line)
452
453
453 @CoroutineInputTransformer.wrap
454 @CoroutineInputTransformer.wrap
454 def classic_prompt():
455 def classic_prompt():
455 """Strip the >>>/... prompts of the Python interactive shell."""
456 """Strip the >>>/... prompts of the Python interactive shell."""
456 # FIXME: non-capturing version (?:...) usable?
457 # FIXME: non-capturing version (?:...) usable?
457 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
458 prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
458 initial_re = re.compile(r'^>>>( |$)')
459 initial_re = re.compile(r'^>>>( |$)')
459 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
460 # Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
460 turnoff_re = re.compile(r'^[%!]')
461 turnoff_re = re.compile(r'^[%!]')
461 return _strip_prompts(prompt_re, initial_re, turnoff_re)
462 return _strip_prompts(prompt_re, initial_re, turnoff_re)
462
463
463 @CoroutineInputTransformer.wrap
464 @CoroutineInputTransformer.wrap
464 def ipy_prompt():
465 def ipy_prompt():
465 """Strip IPython's In [1]:/...: prompts."""
466 """Strip IPython's In [1]:/...: prompts."""
466 # FIXME: non-capturing version (?:...) usable?
467 # FIXME: non-capturing version (?:...) usable?
467 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
468 prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
468 # Disable prompt stripping inside cell magics
469 # Disable prompt stripping inside cell magics
469 turnoff_re = re.compile(r'^%%')
470 turnoff_re = re.compile(r'^%%')
470 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
471 return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
471
472
472
473
473 @CoroutineInputTransformer.wrap
474 @CoroutineInputTransformer.wrap
474 def leading_indent():
475 def leading_indent():
475 """Remove leading indentation.
476 """Remove leading indentation.
476
477
477 If the first line starts with a spaces or tabs, the same whitespace will be
478 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.
479 removed from each following line until it is reset.
479 """
480 """
480 space_re = re.compile(r'^[ \t]+')
481 space_re = re.compile(r'^[ \t]+')
481 line = ''
482 line = ''
482 while True:
483 while True:
483 line = (yield line)
484 line = (yield line)
484
485
485 if line is None:
486 if line is None:
486 continue
487 continue
487
488
488 m = space_re.match(line)
489 m = space_re.match(line)
489 if m:
490 if m:
490 space = m.group(0)
491 space = m.group(0)
491 while line is not None:
492 while line is not None:
492 if line.startswith(space):
493 if line.startswith(space):
493 line = line[len(space):]
494 line = line[len(space):]
494 line = (yield line)
495 line = (yield line)
495 else:
496 else:
496 # No leading spaces - wait for reset
497 # No leading spaces - wait for reset
497 while line is not None:
498 while line is not None:
498 line = (yield line)
499 line = (yield line)
499
500
500
501
501 @CoroutineInputTransformer.wrap
502 @CoroutineInputTransformer.wrap
502 def strip_encoding_cookie():
503 def strip_encoding_cookie():
503 """Remove encoding comment if found in first two lines
504 """Remove encoding comment if found in first two lines
504
505
505 If the first or second line has the `# coding: utf-8` comment,
506 If the first or second line has the `# coding: utf-8` comment,
506 it will be removed.
507 it will be removed.
507 """
508 """
508 line = ''
509 line = ''
509 while True:
510 while True:
510 line = (yield line)
511 line = (yield line)
511 # check comment on first two lines
512 # check comment on first two lines
512 for i in range(2):
513 for i in range(2):
513 if line is None:
514 if line is None:
514 break
515 break
515 if cookie_comment_re.match(line):
516 if cookie_comment_re.match(line):
516 line = (yield "")
517 line = (yield "")
517 else:
518 else:
518 line = (yield line)
519 line = (yield line)
519
520
520 # no-op on the rest of the cell
521 # no-op on the rest of the cell
521 while line is not None:
522 while line is not None:
522 line = (yield line)
523 line = (yield line)
523
524
524 _assign_pat = \
525 _assign_pat = \
525 r'''(?P<lhs>(\s*)
526 r'''(?P<lhs>(\s*)
526 ([\w\.]+) # Initial identifier
527 ([\w\.]+) # Initial identifier
527 (\s*,\s*
528 (\s*,\s*
528 \*?[\w\.]+)* # Further identifiers for unpacking
529 \*?[\w\.]+)* # Further identifiers for unpacking
529 \s*?,? # Trailing comma
530 \s*?,? # Trailing comma
530 )
531 )
531 \s*=\s*
532 \s*=\s*
532 '''
533 '''
533
534
534 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
535 assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
535 assign_system_template = '%s = get_ipython().getoutput(%r)'
536 assign_system_template = '%s = get_ipython().getoutput(%r)'
536 @StatelessInputTransformer.wrap
537 @StatelessInputTransformer.wrap
537 def assign_from_system(line):
538 def assign_from_system(line):
538 """Transform assignment from system commands (e.g. files = !ls)"""
539 """Transform assignment from system commands (e.g. files = !ls)"""
539 m = assign_system_re.match(line)
540 m = assign_system_re.match(line)
540 if m is None:
541 if m is None:
541 return line
542 return line
542
543
543 return assign_system_template % m.group('lhs', 'cmd')
544 return assign_system_template % m.group('lhs', 'cmd')
544
545
545 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
546 assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
546 assign_magic_template = '%s = get_ipython().magic(%r)'
547 assign_magic_template = '%s = get_ipython().magic(%r)'
547 @StatelessInputTransformer.wrap
548 @StatelessInputTransformer.wrap
548 def assign_from_magic(line):
549 def assign_from_magic(line):
549 """Transform assignment from magic commands (e.g. a = %who_ls)"""
550 """Transform assignment from magic commands (e.g. a = %who_ls)"""
550 m = assign_magic_re.match(line)
551 m = assign_magic_re.match(line)
551 if m is None:
552 if m is None:
552 return line
553 return line
553
554
554 return assign_magic_template % m.group('lhs', 'cmd')
555 return assign_magic_template % m.group('lhs', 'cmd')
General Comments 0
You need to be logged in to leave comments. Login now