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