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