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