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