##// END OF EJS Templates
Reorganise InputTransformer decorator architecture.
Thomas Kluyver -
Show More
@@ -1,382 +1,397 b''
1 import abc
1 import abc
2 import functools
2 import re
3 import re
3 from StringIO import StringIO
4 from StringIO import StringIO
4 import tokenize
5 import tokenize
5
6
6 from IPython.core.splitinput import split_user_input, LineInfo
7 from IPython.core.splitinput import split_user_input, LineInfo
7
8
8 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
9 # Globals
10 # Globals
10 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
11
12
12 # The escape sequences that define the syntax transformations IPython will
13 # The escape sequences that define the syntax transformations IPython will
13 # apply to user input. These can NOT be just changed here: many regular
14 # apply to user input. These can NOT be just changed here: many regular
14 # expressions and other parts of the code may use their hardcoded values, and
15 # expressions and other parts of the code may use their hardcoded values, and
15 # for all intents and purposes they constitute the 'IPython syntax', so they
16 # for all intents and purposes they constitute the 'IPython syntax', so they
16 # should be considered fixed.
17 # should be considered fixed.
17
18
18 ESC_SHELL = '!' # Send line to underlying system shell
19 ESC_SHELL = '!' # Send line to underlying system shell
19 ESC_SH_CAP = '!!' # Send line to system shell and capture output
20 ESC_SH_CAP = '!!' # Send line to system shell and capture output
20 ESC_HELP = '?' # Find information about object
21 ESC_HELP = '?' # Find information about object
21 ESC_HELP2 = '??' # Find extra-detailed information about object
22 ESC_HELP2 = '??' # Find extra-detailed information about object
22 ESC_MAGIC = '%' # Call magic function
23 ESC_MAGIC = '%' # Call magic function
23 ESC_MAGIC2 = '%%' # Call cell-magic function
24 ESC_MAGIC2 = '%%' # Call cell-magic function
24 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
25 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
25 ESC_QUOTE2 = ';' # Quote all args as a single string, call
26 ESC_QUOTE2 = ';' # Quote all args as a single string, call
26 ESC_PAREN = '/' # Call first argument with rest of line as arguments
27 ESC_PAREN = '/' # Call first argument with rest of line as arguments
27
28
28 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
29 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
29 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
30 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
30 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
31 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
31
32
32
33
33 class InputTransformer(object):
34 class InputTransformer(object):
34 """Abstract base class for line-based input transformers."""
35 """Abstract base class for line-based input transformers."""
35 __metaclass__ = abc.ABCMeta
36 __metaclass__ = abc.ABCMeta
36
37
37 @abc.abstractmethod
38 @abc.abstractmethod
38 def push(self, line):
39 def push(self, line):
39 """Send a line of input to the transformer, returning the transformed
40 """Send a line of input to the transformer, returning the transformed
40 input or None if the transformer is waiting for more input.
41 input or None if the transformer is waiting for more input.
41
42
42 Must be overridden by subclasses.
43 Must be overridden by subclasses.
43 """
44 """
44 pass
45 pass
45
46
46 @abc.abstractmethod
47 @abc.abstractmethod
47 def reset(self):
48 def reset(self):
48 """Return, transformed any lines that the transformer has accumulated,
49 """Return, transformed any lines that the transformer has accumulated,
49 and reset its internal state.
50 and reset its internal state.
50
51
51 Must be overridden by subclasses.
52 Must be overridden by subclasses.
52 """
53 """
53 pass
54 pass
54
55
55 # Set this to True to allow the transformer to act on lines inside strings.
56 # Set this to True to allow the transformer to act on lines inside strings.
56 look_in_string = False
57 look_in_string = False
57
58 def stateless_input_transformer(func):
59 class StatelessInputTransformer(InputTransformer):
60 """Decorator for a stateless input transformer implemented as a function."""
61 def __init__(self):
62 self.func = func
63
64 def push(self, line):
65 """Send a line of input to the transformer, returning the
66 transformed input."""
67 return self.func(line)
68
69 def reset(self):
70 """No-op - exists for compatibility."""
71 pass
72
58
73 return StatelessInputTransformer
59 @classmethod
74
60 def wrap(cls, func):
75 def coroutine_input_transformer(coro):
61 """Can be used by subclasses as a decorator, to return a factory that
76 class CoroutineInputTransformer(InputTransformer):
62 will allow instantiation with the decorated object.
77 """Wrapper for input transformers based on coroutines."""
63 """
78 def __init__(self):
64 @functools.wraps(func)
79 # Prime it
65 def transformer_factory():
80 self.coro = coro()
66 transformer = cls(func)
81 next(self.coro)
67 if getattr(transformer_factory, 'look_in_string', False):
82
68 transformer.look_in_string = True
83 def push(self, line):
69 return transformer
84 """Send a line of input to the transformer, returning the
85 transformed input or None if the transformer is waiting for more
86 input.
87 """
88 return self.coro.send(line)
89
70
90 def reset(self):
71 return transformer_factory
91 """Return, transformed any lines that the transformer has
72
92 accumulated, and reset its internal state.
73 class StatelessInputTransformer(InputTransformer):
93 """
74 """Wrapper for a stateless input transformer implemented as a function."""
94 return self.coro.send(None)
75 def __init__(self, func):
76 self.func = func
77
78 def __repr__(self):
79 return "StatelessInputTransformer(func={!r})".format(self.func)
95
80
96 return CoroutineInputTransformer
81 def push(self, line):
82 """Send a line of input to the transformer, returning the
83 transformed input."""
84 return self.func(line)
85
86 def reset(self):
87 """No-op - exists for compatibility."""
88 pass
89
90 class CoroutineInputTransformer(InputTransformer):
91 """Wrapper for an input transformer implemented as a coroutine."""
92 def __init__(self, coro):
93 # Prime it
94 self.coro = coro()
95 next(self.coro)
96
97 def __repr__(self):
98 return "CoroutineInputTransformer(coro={!r})".format(self.coro)
99
100 def push(self, line):
101 """Send a line of input to the transformer, returning the
102 transformed input or None if the transformer is waiting for more
103 input.
104 """
105 return self.coro.send(line)
106
107 def reset(self):
108 """Return, transformed any lines that the transformer has
109 accumulated, and reset its internal state.
110 """
111 return self.coro.send(None)
97
112
98
113
99 # Utilities
114 # Utilities
100 def _make_help_call(target, esc, lspace, next_input=None):
115 def _make_help_call(target, esc, lspace, next_input=None):
101 """Prepares a pinfo(2)/psearch call from a target name and the escape
116 """Prepares a pinfo(2)/psearch call from a target name and the escape
102 (i.e. ? or ??)"""
117 (i.e. ? or ??)"""
103 method = 'pinfo2' if esc == '??' \
118 method = 'pinfo2' if esc == '??' \
104 else 'psearch' if '*' in target \
119 else 'psearch' if '*' in target \
105 else 'pinfo'
120 else 'pinfo'
106 arg = " ".join([method, target])
121 arg = " ".join([method, target])
107 if next_input is None:
122 if next_input is None:
108 return '%sget_ipython().magic(%r)' % (lspace, arg)
123 return '%sget_ipython().magic(%r)' % (lspace, arg)
109 else:
124 else:
110 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
125 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
111 (lspace, next_input, arg)
126 (lspace, next_input, arg)
112
127
113 @coroutine_input_transformer
128 @CoroutineInputTransformer.wrap
114 def escaped_transformer():
129 def escaped_transformer():
115 """Translate lines beginning with one of IPython's escape characters.
130 """Translate lines beginning with one of IPython's escape characters.
116
131
117 This is stateful to allow magic commands etc. to be continued over several
132 This is stateful to allow magic commands etc. to be continued over several
118 lines using explicit line continuations (\ at the end of a line).
133 lines using explicit line continuations (\ at the end of a line).
119 """
134 """
120
135
121 # These define the transformations for the different escape characters.
136 # These define the transformations for the different escape characters.
122 def _tr_system(line_info):
137 def _tr_system(line_info):
123 "Translate lines escaped with: !"
138 "Translate lines escaped with: !"
124 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
139 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
125 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
140 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
126
141
127 def _tr_system2(line_info):
142 def _tr_system2(line_info):
128 "Translate lines escaped with: !!"
143 "Translate lines escaped with: !!"
129 cmd = line_info.line.lstrip()[2:]
144 cmd = line_info.line.lstrip()[2:]
130 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
145 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
131
146
132 def _tr_help(line_info):
147 def _tr_help(line_info):
133 "Translate lines escaped with: ?/??"
148 "Translate lines escaped with: ?/??"
134 # A naked help line should just fire the intro help screen
149 # A naked help line should just fire the intro help screen
135 if not line_info.line[1:]:
150 if not line_info.line[1:]:
136 return 'get_ipython().show_usage()'
151 return 'get_ipython().show_usage()'
137
152
138 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
153 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
139
154
140 def _tr_magic(line_info):
155 def _tr_magic(line_info):
141 "Translate lines escaped with: %"
156 "Translate lines escaped with: %"
142 tpl = '%sget_ipython().magic(%r)'
157 tpl = '%sget_ipython().magic(%r)'
143 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
158 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
144 return tpl % (line_info.pre, cmd)
159 return tpl % (line_info.pre, cmd)
145
160
146 def _tr_quote(line_info):
161 def _tr_quote(line_info):
147 "Translate lines escaped with: ,"
162 "Translate lines escaped with: ,"
148 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
163 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
149 '", "'.join(line_info.the_rest.split()) )
164 '", "'.join(line_info.the_rest.split()) )
150
165
151 def _tr_quote2(line_info):
166 def _tr_quote2(line_info):
152 "Translate lines escaped with: ;"
167 "Translate lines escaped with: ;"
153 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
168 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
154 line_info.the_rest)
169 line_info.the_rest)
155
170
156 def _tr_paren(line_info):
171 def _tr_paren(line_info):
157 "Translate lines escaped with: /"
172 "Translate lines escaped with: /"
158 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
173 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
159 ", ".join(line_info.the_rest.split()))
174 ", ".join(line_info.the_rest.split()))
160
175
161 tr = { ESC_SHELL : _tr_system,
176 tr = { ESC_SHELL : _tr_system,
162 ESC_SH_CAP : _tr_system2,
177 ESC_SH_CAP : _tr_system2,
163 ESC_HELP : _tr_help,
178 ESC_HELP : _tr_help,
164 ESC_HELP2 : _tr_help,
179 ESC_HELP2 : _tr_help,
165 ESC_MAGIC : _tr_magic,
180 ESC_MAGIC : _tr_magic,
166 ESC_QUOTE : _tr_quote,
181 ESC_QUOTE : _tr_quote,
167 ESC_QUOTE2 : _tr_quote2,
182 ESC_QUOTE2 : _tr_quote2,
168 ESC_PAREN : _tr_paren }
183 ESC_PAREN : _tr_paren }
169
184
170 line = ''
185 line = ''
171 while True:
186 while True:
172 line = (yield line)
187 line = (yield line)
173 if not line or line.isspace():
188 if not line or line.isspace():
174 continue
189 continue
175 lineinf = LineInfo(line)
190 lineinf = LineInfo(line)
176 if lineinf.esc not in tr:
191 if lineinf.esc not in tr:
177 continue
192 continue
178
193
179 parts = []
194 parts = []
180 while line is not None:
195 while line is not None:
181 parts.append(line.rstrip('\\'))
196 parts.append(line.rstrip('\\'))
182 if not line.endswith('\\'):
197 if not line.endswith('\\'):
183 break
198 break
184 line = (yield None)
199 line = (yield None)
185
200
186 # Output
201 # Output
187 lineinf = LineInfo(' '.join(parts))
202 lineinf = LineInfo(' '.join(parts))
188 line = tr[lineinf.esc](lineinf)
203 line = tr[lineinf.esc](lineinf)
189
204
190 _initial_space_re = re.compile(r'\s*')
205 _initial_space_re = re.compile(r'\s*')
191
206
192 _help_end_re = re.compile(r"""(%{0,2}
207 _help_end_re = re.compile(r"""(%{0,2}
193 [a-zA-Z_*][\w*]* # Variable name
208 [a-zA-Z_*][\w*]* # Variable name
194 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
209 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
195 )
210 )
196 (\?\??)$ # ? or ??""",
211 (\?\??)$ # ? or ??""",
197 re.VERBOSE)
212 re.VERBOSE)
198
213
199 def has_comment(src):
214 def has_comment(src):
200 """Indicate whether an input line has (i.e. ends in, or is) a comment.
215 """Indicate whether an input line has (i.e. ends in, or is) a comment.
201
216
202 This uses tokenize, so it can distinguish comments from # inside strings.
217 This uses tokenize, so it can distinguish comments from # inside strings.
203
218
204 Parameters
219 Parameters
205 ----------
220 ----------
206 src : string
221 src : string
207 A single line input string.
222 A single line input string.
208
223
209 Returns
224 Returns
210 -------
225 -------
211 Boolean: True if source has a comment.
226 Boolean: True if source has a comment.
212 """
227 """
213 readline = StringIO(src).readline
228 readline = StringIO(src).readline
214 toktypes = set()
229 toktypes = set()
215 try:
230 try:
216 for t in tokenize.generate_tokens(readline):
231 for t in tokenize.generate_tokens(readline):
217 toktypes.add(t[0])
232 toktypes.add(t[0])
218 except tokenize.TokenError:
233 except tokenize.TokenError:
219 pass
234 pass
220 return(tokenize.COMMENT in toktypes)
235 return(tokenize.COMMENT in toktypes)
221
236
222
237
223 @stateless_input_transformer
238 @StatelessInputTransformer.wrap
224 def help_end(line):
239 def help_end(line):
225 """Translate lines with ?/?? at the end"""
240 """Translate lines with ?/?? at the end"""
226 m = _help_end_re.search(line)
241 m = _help_end_re.search(line)
227 if m is None or has_comment(line):
242 if m is None or has_comment(line):
228 return line
243 return line
229 target = m.group(1)
244 target = m.group(1)
230 esc = m.group(3)
245 esc = m.group(3)
231 lspace = _initial_space_re.match(line).group(0)
246 lspace = _initial_space_re.match(line).group(0)
232
247
233 # If we're mid-command, put it back on the next prompt for the user.
248 # If we're mid-command, put it back on the next prompt for the user.
234 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
249 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
235
250
236 return _make_help_call(target, esc, lspace, next_input)
251 return _make_help_call(target, esc, lspace, next_input)
237
252
238
253
239 @coroutine_input_transformer
254 @CoroutineInputTransformer.wrap
240 def cellmagic():
255 def cellmagic():
241 """Captures & transforms cell magics.
256 """Captures & transforms cell magics.
242
257
243 After a cell magic is started, this stores up any lines it gets until it is
258 After a cell magic is started, this stores up any lines it gets until it is
244 reset (sent None).
259 reset (sent None).
245 """
260 """
246 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
261 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
247 cellmagic_help_re = re.compile('%%\w+\?')
262 cellmagic_help_re = re.compile('%%\w+\?')
248 line = ''
263 line = ''
249 while True:
264 while True:
250 line = (yield line)
265 line = (yield line)
251 if (not line) or (not line.startswith(ESC_MAGIC2)):
266 if (not line) or (not line.startswith(ESC_MAGIC2)):
252 continue
267 continue
253
268
254 if cellmagic_help_re.match(line):
269 if cellmagic_help_re.match(line):
255 # This case will be handled by help_end
270 # This case will be handled by help_end
256 continue
271 continue
257
272
258 first = line
273 first = line
259 body = []
274 body = []
260 line = (yield None)
275 line = (yield None)
261 while (line is not None) and (line.strip() != ''):
276 while (line is not None) and (line.strip() != ''):
262 body.append(line)
277 body.append(line)
263 line = (yield None)
278 line = (yield None)
264
279
265 # Output
280 # Output
266 magic_name, _, first = first.partition(' ')
281 magic_name, _, first = first.partition(' ')
267 magic_name = magic_name.lstrip(ESC_MAGIC2)
282 magic_name = magic_name.lstrip(ESC_MAGIC2)
268 line = tpl % (magic_name, first, u'\n'.join(body))
283 line = tpl % (magic_name, first, u'\n'.join(body))
269
284
270
285
271 def _strip_prompts(prompt1_re, prompt2_re):
286 def _strip_prompts(prompt1_re, prompt2_re):
272 """Remove matching input prompts from a block of input."""
287 """Remove matching input prompts from a block of input."""
273 line = ''
288 line = ''
274 while True:
289 while True:
275 line = (yield line)
290 line = (yield line)
276
291
277 if line is None:
292 if line is None:
278 continue
293 continue
279
294
280 m = prompt1_re.match(line)
295 m = prompt1_re.match(line)
281 if m:
296 if m:
282 while m:
297 while m:
283 line = (yield line[len(m.group(0)):])
298 line = (yield line[len(m.group(0)):])
284 if line is None:
299 if line is None:
285 break
300 break
286 m = prompt2_re.match(line)
301 m = prompt2_re.match(line)
287 else:
302 else:
288 # Prompts not in input - wait for reset
303 # Prompts not in input - wait for reset
289 while line is not None:
304 while line is not None:
290 line = (yield line)
305 line = (yield line)
291
306
292 @coroutine_input_transformer
307 @CoroutineInputTransformer.wrap
293 def classic_prompt():
308 def classic_prompt():
294 """Strip the >>>/... prompts of the Python interactive shell."""
309 """Strip the >>>/... prompts of the Python interactive shell."""
295 prompt1_re = re.compile(r'^(>>> )')
310 prompt1_re = re.compile(r'^(>>> )')
296 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
311 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
297 return _strip_prompts(prompt1_re, prompt2_re)
312 return _strip_prompts(prompt1_re, prompt2_re)
298
313
299 classic_prompt.look_in_string = True
314 classic_prompt.look_in_string = True
300
315
301 @coroutine_input_transformer
316 @CoroutineInputTransformer.wrap
302 def ipy_prompt():
317 def ipy_prompt():
303 """Strip IPython's In [1]:/...: prompts."""
318 """Strip IPython's In [1]:/...: prompts."""
304 prompt1_re = re.compile(r'^In \[\d+\]: ')
319 prompt1_re = re.compile(r'^In \[\d+\]: ')
305 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
320 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
306 return _strip_prompts(prompt1_re, prompt2_re)
321 return _strip_prompts(prompt1_re, prompt2_re)
307
322
308 ipy_prompt.look_in_string = True
323 ipy_prompt.look_in_string = True
309
324
310
325
311 @coroutine_input_transformer
326 @CoroutineInputTransformer.wrap
312 def leading_indent():
327 def leading_indent():
313 """Remove leading indentation.
328 """Remove leading indentation.
314
329
315 If the first line starts with a spaces or tabs, the same whitespace will be
330 If the first line starts with a spaces or tabs, the same whitespace will be
316 removed from each following line until it is reset.
331 removed from each following line until it is reset.
317 """
332 """
318 space_re = re.compile(r'^[ \t]+')
333 space_re = re.compile(r'^[ \t]+')
319 line = ''
334 line = ''
320 while True:
335 while True:
321 line = (yield line)
336 line = (yield line)
322
337
323 if line is None:
338 if line is None:
324 continue
339 continue
325
340
326 m = space_re.match(line)
341 m = space_re.match(line)
327 if m:
342 if m:
328 space = m.group(0)
343 space = m.group(0)
329 while line is not None:
344 while line is not None:
330 if line.startswith(space):
345 if line.startswith(space):
331 line = line[len(space):]
346 line = line[len(space):]
332 line = (yield line)
347 line = (yield line)
333 else:
348 else:
334 # No leading spaces - wait for reset
349 # No leading spaces - wait for reset
335 while line is not None:
350 while line is not None:
336 line = (yield line)
351 line = (yield line)
337
352
338 leading_indent.look_in_string = True
353 leading_indent.look_in_string = True
339
354
340
355
341 def _special_assignment(assignment_re, template):
356 def _special_assignment(assignment_re, template):
342 """Transform assignment from system & magic commands.
357 """Transform assignment from system & magic commands.
343
358
344 This is stateful so that it can handle magic commands continued on several
359 This is stateful so that it can handle magic commands continued on several
345 lines.
360 lines.
346 """
361 """
347 line = ''
362 line = ''
348 while True:
363 while True:
349 line = (yield line)
364 line = (yield line)
350 if not line or line.isspace():
365 if not line or line.isspace():
351 continue
366 continue
352
367
353 m = assignment_re.match(line)
368 m = assignment_re.match(line)
354 if not m:
369 if not m:
355 continue
370 continue
356
371
357 parts = []
372 parts = []
358 while line is not None:
373 while line is not None:
359 parts.append(line.rstrip('\\'))
374 parts.append(line.rstrip('\\'))
360 if not line.endswith('\\'):
375 if not line.endswith('\\'):
361 break
376 break
362 line = (yield None)
377 line = (yield None)
363
378
364 # Output
379 # Output
365 whole = assignment_re.match(' '.join(parts))
380 whole = assignment_re.match(' '.join(parts))
366 line = template % (whole.group('lhs'), whole.group('cmd'))
381 line = template % (whole.group('lhs'), whole.group('cmd'))
367
382
368 @coroutine_input_transformer
383 @CoroutineInputTransformer.wrap
369 def assign_from_system():
384 def assign_from_system():
370 """Transform assignment from system commands (e.g. files = !ls)"""
385 """Transform assignment from system commands (e.g. files = !ls)"""
371 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
386 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
372 r'\s*=\s*!\s*(?P<cmd>.*)')
387 r'\s*=\s*!\s*(?P<cmd>.*)')
373 template = '%s = get_ipython().getoutput(%r)'
388 template = '%s = get_ipython().getoutput(%r)'
374 return _special_assignment(assignment_re, template)
389 return _special_assignment(assignment_re, template)
375
390
376 @coroutine_input_transformer
391 @CoroutineInputTransformer.wrap
377 def assign_from_magic():
392 def assign_from_magic():
378 """Transform assignment from magic commands (e.g. a = %who_ls)"""
393 """Transform assignment from magic commands (e.g. a = %who_ls)"""
379 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
394 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
380 r'\s*=\s*%\s*(?P<cmd>.*)')
395 r'\s*=\s*%\s*(?P<cmd>.*)')
381 template = '%s = get_ipython().magic(%r)'
396 template = '%s = get_ipython().magic(%r)'
382 return _special_assignment(assignment_re, template)
397 return _special_assignment(assignment_re, template)
General Comments 0
You need to be logged in to leave comments. Login now