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