##// END OF EJS Templates
add docstring, and emit deprecation warnings
Matthias Bussonnier -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,768 +1,773 b''
1 1 """DEPRECATED: Input handling and transformation machinery.
2 2
3 3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
4 4
5 5 The first class in this module, :class:`InputSplitter`, is designed to tell when
6 6 input from a line-oriented frontend is complete and should be executed, and when
7 7 the user should be prompted for another line of code instead. The name 'input
8 8 splitter' is largely for historical reasons.
9 9
10 10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
11 11 with full support for the extended IPython syntax (magics, system calls, etc).
12 12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
13 13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
14 14 and stores the results.
15 15
16 16 For more details, see the class docstrings below.
17 17 """
18 18
19 from warnings import warn
20
21 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
22 DeprecationWarning)
23
19 24 # Copyright (c) IPython Development Team.
20 25 # Distributed under the terms of the Modified BSD License.
21 26 import ast
22 27 import codeop
23 28 import io
24 29 import re
25 30 import sys
26 31 import tokenize
27 32 import warnings
28 33
29 34 from IPython.utils.py3compat import cast_unicode
30 35 from IPython.core.inputtransformer import (leading_indent,
31 36 classic_prompt,
32 37 ipy_prompt,
33 38 cellmagic,
34 39 assemble_logical_lines,
35 40 help_end,
36 41 escaped_commands,
37 42 assign_from_magic,
38 43 assign_from_system,
39 44 assemble_python_lines,
40 45 )
41 46
42 47 # These are available in this module for backwards compatibility.
43 48 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
44 49 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
45 50 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
46 51
47 52 #-----------------------------------------------------------------------------
48 53 # Utilities
49 54 #-----------------------------------------------------------------------------
50 55
51 56 # FIXME: These are general-purpose utilities that later can be moved to the
52 57 # general ward. Kept here for now because we're being very strict about test
53 58 # coverage with this code, and this lets us ensure that we keep 100% coverage
54 59 # while developing.
55 60
56 61 # compiled regexps for autoindent management
57 62 dedent_re = re.compile('|'.join([
58 63 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
59 64 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
60 65 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
61 66 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
62 67 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
63 68 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
64 69 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
65 70 ]))
66 71 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
67 72
68 73 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
69 74 # before pure comments
70 75 comment_line_re = re.compile('^\s*\#')
71 76
72 77
73 78 def num_ini_spaces(s):
74 79 """Return the number of initial spaces in a string.
75 80
76 81 Note that tabs are counted as a single space. For now, we do *not* support
77 82 mixing of tabs and spaces in the user's input.
78 83
79 84 Parameters
80 85 ----------
81 86 s : string
82 87
83 88 Returns
84 89 -------
85 90 n : int
86 91 """
87 92
88 93 ini_spaces = ini_spaces_re.match(s)
89 94 if ini_spaces:
90 95 return ini_spaces.end()
91 96 else:
92 97 return 0
93 98
94 99 # Fake token types for partial_tokenize:
95 100 INCOMPLETE_STRING = tokenize.N_TOKENS
96 101 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
97 102
98 103 # The 2 classes below have the same API as TokenInfo, but don't try to look up
99 104 # a token type name that they won't find.
100 105 class IncompleteString:
101 106 type = exact_type = INCOMPLETE_STRING
102 107 def __init__(self, s, start, end, line):
103 108 self.s = s
104 109 self.start = start
105 110 self.end = end
106 111 self.line = line
107 112
108 113 class InMultilineStatement:
109 114 type = exact_type = IN_MULTILINE_STATEMENT
110 115 def __init__(self, pos, line):
111 116 self.s = ''
112 117 self.start = self.end = pos
113 118 self.line = line
114 119
115 120 def partial_tokens(s):
116 121 """Iterate over tokens from a possibly-incomplete string of code.
117 122
118 123 This adds two special token types: INCOMPLETE_STRING and
119 124 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
120 125 represent the two main ways for code to be incomplete.
121 126 """
122 127 readline = io.StringIO(s).readline
123 128 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
124 129 try:
125 130 for token in tokenize.generate_tokens(readline):
126 131 yield token
127 132 except tokenize.TokenError as e:
128 133 # catch EOF error
129 134 lines = s.splitlines(keepends=True)
130 135 end = len(lines), len(lines[-1])
131 136 if 'multi-line string' in e.args[0]:
132 137 l, c = start = token.end
133 138 s = lines[l-1][c:] + ''.join(lines[l:])
134 139 yield IncompleteString(s, start, end, lines[-1])
135 140 elif 'multi-line statement' in e.args[0]:
136 141 yield InMultilineStatement(end, lines[-1])
137 142 else:
138 143 raise
139 144
140 145 def find_next_indent(code):
141 146 """Find the number of spaces for the next line of indentation"""
142 147 tokens = list(partial_tokens(code))
143 148 if tokens[-1].type == tokenize.ENDMARKER:
144 149 tokens.pop()
145 150 if not tokens:
146 151 return 0
147 152 while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}):
148 153 tokens.pop()
149 154
150 155 if tokens[-1].type == INCOMPLETE_STRING:
151 156 # Inside a multiline string
152 157 return 0
153 158
154 159 # Find the indents used before
155 160 prev_indents = [0]
156 161 def _add_indent(n):
157 162 if n != prev_indents[-1]:
158 163 prev_indents.append(n)
159 164
160 165 tokiter = iter(tokens)
161 166 for tok in tokiter:
162 167 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
163 168 _add_indent(tok.end[1])
164 169 elif (tok.type == tokenize.NL):
165 170 try:
166 171 _add_indent(next(tokiter).start[1])
167 172 except StopIteration:
168 173 break
169 174
170 175 last_indent = prev_indents.pop()
171 176
172 177 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
173 178 if tokens[-1].type == IN_MULTILINE_STATEMENT:
174 179 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
175 180 return last_indent + 4
176 181 return last_indent
177 182
178 183 if tokens[-1].exact_type == tokenize.COLON:
179 184 # Line ends with colon - indent
180 185 return last_indent + 4
181 186
182 187 if last_indent:
183 188 # Examine the last line for dedent cues - statements like return or
184 189 # raise which normally end a block of code.
185 190 last_line_starts = 0
186 191 for i, tok in enumerate(tokens):
187 192 if tok.type == tokenize.NEWLINE:
188 193 last_line_starts = i + 1
189 194
190 195 last_line_tokens = tokens[last_line_starts:]
191 196 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
192 197 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
193 198 # Find the most recent indentation less than the current level
194 199 for indent in reversed(prev_indents):
195 200 if indent < last_indent:
196 201 return indent
197 202
198 203 return last_indent
199 204
200 205
201 206 def last_blank(src):
202 207 """Determine if the input source ends in a blank.
203 208
204 209 A blank is either a newline or a line consisting of whitespace.
205 210
206 211 Parameters
207 212 ----------
208 213 src : string
209 214 A single or multiline string.
210 215 """
211 216 if not src: return False
212 217 ll = src.splitlines()[-1]
213 218 return (ll == '') or ll.isspace()
214 219
215 220
216 221 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
217 222 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
218 223
219 224 def last_two_blanks(src):
220 225 """Determine if the input source ends in two blanks.
221 226
222 227 A blank is either a newline or a line consisting of whitespace.
223 228
224 229 Parameters
225 230 ----------
226 231 src : string
227 232 A single or multiline string.
228 233 """
229 234 if not src: return False
230 235 # The logic here is tricky: I couldn't get a regexp to work and pass all
231 236 # the tests, so I took a different approach: split the source by lines,
232 237 # grab the last two and prepend '###\n' as a stand-in for whatever was in
233 238 # the body before the last two lines. Then, with that structure, it's
234 239 # possible to analyze with two regexps. Not the most elegant solution, but
235 240 # it works. If anyone tries to change this logic, make sure to validate
236 241 # the whole test suite first!
237 242 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
238 243 return (bool(last_two_blanks_re.match(new_src)) or
239 244 bool(last_two_blanks_re2.match(new_src)) )
240 245
241 246
242 247 def remove_comments(src):
243 248 """Remove all comments from input source.
244 249
245 250 Note: comments are NOT recognized inside of strings!
246 251
247 252 Parameters
248 253 ----------
249 254 src : string
250 255 A single or multiline input string.
251 256
252 257 Returns
253 258 -------
254 259 String with all Python comments removed.
255 260 """
256 261
257 262 return re.sub('#.*', '', src)
258 263
259 264
260 265 def get_input_encoding():
261 266 """Return the default standard input encoding.
262 267
263 268 If sys.stdin has no encoding, 'ascii' is returned."""
264 269 # There are strange environments for which sys.stdin.encoding is None. We
265 270 # ensure that a valid encoding is returned.
266 271 encoding = getattr(sys.stdin, 'encoding', None)
267 272 if encoding is None:
268 273 encoding = 'ascii'
269 274 return encoding
270 275
271 276 #-----------------------------------------------------------------------------
272 277 # Classes and functions for normal Python syntax handling
273 278 #-----------------------------------------------------------------------------
274 279
275 280 class InputSplitter(object):
276 281 r"""An object that can accumulate lines of Python source before execution.
277 282
278 283 This object is designed to be fed python source line-by-line, using
279 284 :meth:`push`. It will return on each push whether the currently pushed
280 285 code could be executed already. In addition, it provides a method called
281 286 :meth:`push_accepts_more` that can be used to query whether more input
282 287 can be pushed into a single interactive block.
283 288
284 289 This is a simple example of how an interactive terminal-based client can use
285 290 this tool::
286 291
287 292 isp = InputSplitter()
288 293 while isp.push_accepts_more():
289 294 indent = ' '*isp.indent_spaces
290 295 prompt = '>>> ' + indent
291 296 line = indent + raw_input(prompt)
292 297 isp.push(line)
293 298 print 'Input source was:\n', isp.source_reset(),
294 299 """
295 300 # A cache for storing the current indentation
296 301 # The first value stores the most recently processed source input
297 302 # The second value is the number of spaces for the current indentation
298 303 # If self.source matches the first value, the second value is a valid
299 304 # current indentation. Otherwise, the cache is invalid and the indentation
300 305 # must be recalculated.
301 306 _indent_spaces_cache = None, None
302 307 # String, indicating the default input encoding. It is computed by default
303 308 # at initialization time via get_input_encoding(), but it can be reset by a
304 309 # client with specific knowledge of the encoding.
305 310 encoding = ''
306 311 # String where the current full source input is stored, properly encoded.
307 312 # Reading this attribute is the normal way of querying the currently pushed
308 313 # source code, that has been properly encoded.
309 314 source = ''
310 315 # Code object corresponding to the current source. It is automatically
311 316 # synced to the source, so it can be queried at any time to obtain the code
312 317 # object; it will be None if the source doesn't compile to valid Python.
313 318 code = None
314 319
315 320 # Private attributes
316 321
317 322 # List with lines of input accumulated so far
318 323 _buffer = None
319 324 # Command compiler
320 325 _compile = None
321 326 # Boolean indicating whether the current block is complete
322 327 _is_complete = None
323 328 # Boolean indicating whether the current block has an unrecoverable syntax error
324 329 _is_invalid = False
325 330
326 331 def __init__(self):
327 332 """Create a new InputSplitter instance.
328 333 """
329 334 self._buffer = []
330 335 self._compile = codeop.CommandCompiler()
331 336 self.encoding = get_input_encoding()
332 337
333 338 def reset(self):
334 339 """Reset the input buffer and associated state."""
335 340 self._buffer[:] = []
336 341 self.source = ''
337 342 self.code = None
338 343 self._is_complete = False
339 344 self._is_invalid = False
340 345
341 346 def source_reset(self):
342 347 """Return the input source and perform a full reset.
343 348 """
344 349 out = self.source
345 350 self.reset()
346 351 return out
347 352
348 353 def check_complete(self, source):
349 354 """Return whether a block of code is ready to execute, or should be continued
350 355
351 356 This is a non-stateful API, and will reset the state of this InputSplitter.
352 357
353 358 Parameters
354 359 ----------
355 360 source : string
356 361 Python input code, which can be multiline.
357 362
358 363 Returns
359 364 -------
360 365 status : str
361 366 One of 'complete', 'incomplete', or 'invalid' if source is not a
362 367 prefix of valid code.
363 368 indent_spaces : int or None
364 369 The number of spaces by which to indent the next line of code. If
365 370 status is not 'incomplete', this is None.
366 371 """
367 372 self.reset()
368 373 try:
369 374 self.push(source)
370 375 except SyntaxError:
371 376 # Transformers in IPythonInputSplitter can raise SyntaxError,
372 377 # which push() will not catch.
373 378 return 'invalid', None
374 379 else:
375 380 if self._is_invalid:
376 381 return 'invalid', None
377 382 elif self.push_accepts_more():
378 383 return 'incomplete', self.get_indent_spaces()
379 384 else:
380 385 return 'complete', None
381 386 finally:
382 387 self.reset()
383 388
384 389 def push(self, lines):
385 390 """Push one or more lines of input.
386 391
387 392 This stores the given lines and returns a status code indicating
388 393 whether the code forms a complete Python block or not.
389 394
390 395 Any exceptions generated in compilation are swallowed, but if an
391 396 exception was produced, the method returns True.
392 397
393 398 Parameters
394 399 ----------
395 400 lines : string
396 401 One or more lines of Python input.
397 402
398 403 Returns
399 404 -------
400 405 is_complete : boolean
401 406 True if the current input source (the result of the current input
402 407 plus prior inputs) forms a complete Python execution block. Note that
403 408 this value is also stored as a private attribute (``_is_complete``), so it
404 409 can be queried at any time.
405 410 """
406 411 self._store(lines)
407 412 source = self.source
408 413
409 414 # Before calling _compile(), reset the code object to None so that if an
410 415 # exception is raised in compilation, we don't mislead by having
411 416 # inconsistent code/source attributes.
412 417 self.code, self._is_complete = None, None
413 418 self._is_invalid = False
414 419
415 420 # Honor termination lines properly
416 421 if source.endswith('\\\n'):
417 422 return False
418 423
419 424 try:
420 425 with warnings.catch_warnings():
421 426 warnings.simplefilter('error', SyntaxWarning)
422 427 self.code = self._compile(source, symbol="exec")
423 428 # Invalid syntax can produce any of a number of different errors from
424 429 # inside the compiler, so we have to catch them all. Syntax errors
425 430 # immediately produce a 'ready' block, so the invalid Python can be
426 431 # sent to the kernel for evaluation with possible ipython
427 432 # special-syntax conversion.
428 433 except (SyntaxError, OverflowError, ValueError, TypeError,
429 434 MemoryError, SyntaxWarning):
430 435 self._is_complete = True
431 436 self._is_invalid = True
432 437 else:
433 438 # Compilation didn't produce any exceptions (though it may not have
434 439 # given a complete code object)
435 440 self._is_complete = self.code is not None
436 441
437 442 return self._is_complete
438 443
439 444 def push_accepts_more(self):
440 445 """Return whether a block of interactive input can accept more input.
441 446
442 447 This method is meant to be used by line-oriented frontends, who need to
443 448 guess whether a block is complete or not based solely on prior and
444 449 current input lines. The InputSplitter considers it has a complete
445 450 interactive block and will not accept more input when either:
446 451
447 452 * A SyntaxError is raised
448 453
449 454 * The code is complete and consists of a single line or a single
450 455 non-compound statement
451 456
452 457 * The code is complete and has a blank line at the end
453 458
454 459 If the current input produces a syntax error, this method immediately
455 460 returns False but does *not* raise the syntax error exception, as
456 461 typically clients will want to send invalid syntax to an execution
457 462 backend which might convert the invalid syntax into valid Python via
458 463 one of the dynamic IPython mechanisms.
459 464 """
460 465
461 466 # With incomplete input, unconditionally accept more
462 467 # A syntax error also sets _is_complete to True - see push()
463 468 if not self._is_complete:
464 469 #print("Not complete") # debug
465 470 return True
466 471
467 472 # The user can make any (complete) input execute by leaving a blank line
468 473 last_line = self.source.splitlines()[-1]
469 474 if (not last_line) or last_line.isspace():
470 475 #print("Blank line") # debug
471 476 return False
472 477
473 478 # If there's just a single line or AST node, and we're flush left, as is
474 479 # the case after a simple statement such as 'a=1', we want to execute it
475 480 # straight away.
476 481 if self.get_indent_spaces() == 0:
477 482 if len(self.source.splitlines()) <= 1:
478 483 return False
479 484
480 485 try:
481 486 code_ast = ast.parse(u''.join(self._buffer))
482 487 except Exception:
483 488 #print("Can't parse AST") # debug
484 489 return False
485 490 else:
486 491 if len(code_ast.body) == 1 and \
487 492 not hasattr(code_ast.body[0], 'body'):
488 493 #print("Simple statement") # debug
489 494 return False
490 495
491 496 # General fallback - accept more code
492 497 return True
493 498
494 499 def get_indent_spaces(self):
495 500 sourcefor, n = self._indent_spaces_cache
496 501 if sourcefor == self.source:
497 502 return n
498 503
499 504 # self.source always has a trailing newline
500 505 n = find_next_indent(self.source[:-1])
501 506 self._indent_spaces_cache = (self.source, n)
502 507 return n
503 508
504 509 # Backwards compatibility. I think all code that used .indent_spaces was
505 510 # inside IPython, but we can leave this here until IPython 7 in case any
506 511 # other modules are using it. -TK, November 2017
507 512 indent_spaces = property(get_indent_spaces)
508 513
509 514 def _store(self, lines, buffer=None, store='source'):
510 515 """Store one or more lines of input.
511 516
512 517 If input lines are not newline-terminated, a newline is automatically
513 518 appended."""
514 519
515 520 if buffer is None:
516 521 buffer = self._buffer
517 522
518 523 if lines.endswith('\n'):
519 524 buffer.append(lines)
520 525 else:
521 526 buffer.append(lines+'\n')
522 527 setattr(self, store, self._set_source(buffer))
523 528
524 529 def _set_source(self, buffer):
525 530 return u''.join(buffer)
526 531
527 532
528 533 class IPythonInputSplitter(InputSplitter):
529 534 """An input splitter that recognizes all of IPython's special syntax."""
530 535
531 536 # String with raw, untransformed input.
532 537 source_raw = ''
533 538
534 539 # Flag to track when a transformer has stored input that it hasn't given
535 540 # back yet.
536 541 transformer_accumulating = False
537 542
538 543 # Flag to track when assemble_python_lines has stored input that it hasn't
539 544 # given back yet.
540 545 within_python_line = False
541 546
542 547 # Private attributes
543 548
544 549 # List with lines of raw input accumulated so far.
545 550 _buffer_raw = None
546 551
547 552 def __init__(self, line_input_checker=True, physical_line_transforms=None,
548 553 logical_line_transforms=None, python_line_transforms=None):
549 554 super(IPythonInputSplitter, self).__init__()
550 555 self._buffer_raw = []
551 556 self._validate = True
552 557
553 558 if physical_line_transforms is not None:
554 559 self.physical_line_transforms = physical_line_transforms
555 560 else:
556 561 self.physical_line_transforms = [
557 562 leading_indent(),
558 563 classic_prompt(),
559 564 ipy_prompt(),
560 565 cellmagic(end_on_blank_line=line_input_checker),
561 566 ]
562 567
563 568 self.assemble_logical_lines = assemble_logical_lines()
564 569 if logical_line_transforms is not None:
565 570 self.logical_line_transforms = logical_line_transforms
566 571 else:
567 572 self.logical_line_transforms = [
568 573 help_end(),
569 574 escaped_commands(),
570 575 assign_from_magic(),
571 576 assign_from_system(),
572 577 ]
573 578
574 579 self.assemble_python_lines = assemble_python_lines()
575 580 if python_line_transforms is not None:
576 581 self.python_line_transforms = python_line_transforms
577 582 else:
578 583 # We don't use any of these at present
579 584 self.python_line_transforms = []
580 585
581 586 @property
582 587 def transforms(self):
583 588 "Quick access to all transformers."
584 589 return self.physical_line_transforms + \
585 590 [self.assemble_logical_lines] + self.logical_line_transforms + \
586 591 [self.assemble_python_lines] + self.python_line_transforms
587 592
588 593 @property
589 594 def transforms_in_use(self):
590 595 """Transformers, excluding logical line transformers if we're in a
591 596 Python line."""
592 597 t = self.physical_line_transforms[:]
593 598 if not self.within_python_line:
594 599 t += [self.assemble_logical_lines] + self.logical_line_transforms
595 600 return t + [self.assemble_python_lines] + self.python_line_transforms
596 601
597 602 def reset(self):
598 603 """Reset the input buffer and associated state."""
599 604 super(IPythonInputSplitter, self).reset()
600 605 self._buffer_raw[:] = []
601 606 self.source_raw = ''
602 607 self.transformer_accumulating = False
603 608 self.within_python_line = False
604 609
605 610 for t in self.transforms:
606 611 try:
607 612 t.reset()
608 613 except SyntaxError:
609 614 # Nothing that calls reset() expects to handle transformer
610 615 # errors
611 616 pass
612 617
613 618 def flush_transformers(self):
614 619 def _flush(transform, outs):
615 620 """yield transformed lines
616 621
617 622 always strings, never None
618 623
619 624 transform: the current transform
620 625 outs: an iterable of previously transformed inputs.
621 626 Each may be multiline, which will be passed
622 627 one line at a time to transform.
623 628 """
624 629 for out in outs:
625 630 for line in out.splitlines():
626 631 # push one line at a time
627 632 tmp = transform.push(line)
628 633 if tmp is not None:
629 634 yield tmp
630 635
631 636 # reset the transform
632 637 tmp = transform.reset()
633 638 if tmp is not None:
634 639 yield tmp
635 640
636 641 out = []
637 642 for t in self.transforms_in_use:
638 643 out = _flush(t, out)
639 644
640 645 out = list(out)
641 646 if out:
642 647 self._store('\n'.join(out))
643 648
644 649 def raw_reset(self):
645 650 """Return raw input only and perform a full reset.
646 651 """
647 652 out = self.source_raw
648 653 self.reset()
649 654 return out
650 655
651 656 def source_reset(self):
652 657 try:
653 658 self.flush_transformers()
654 659 return self.source
655 660 finally:
656 661 self.reset()
657 662
658 663 def push_accepts_more(self):
659 664 if self.transformer_accumulating:
660 665 return True
661 666 else:
662 667 return super(IPythonInputSplitter, self).push_accepts_more()
663 668
664 669 def transform_cell(self, cell):
665 670 """Process and translate a cell of input.
666 671 """
667 672 self.reset()
668 673 try:
669 674 self.push(cell)
670 675 self.flush_transformers()
671 676 return self.source
672 677 finally:
673 678 self.reset()
674 679
675 680 def push(self, lines):
676 681 """Push one or more lines of IPython input.
677 682
678 683 This stores the given lines and returns a status code indicating
679 684 whether the code forms a complete Python block or not, after processing
680 685 all input lines for special IPython syntax.
681 686
682 687 Any exceptions generated in compilation are swallowed, but if an
683 688 exception was produced, the method returns True.
684 689
685 690 Parameters
686 691 ----------
687 692 lines : string
688 693 One or more lines of Python input.
689 694
690 695 Returns
691 696 -------
692 697 is_complete : boolean
693 698 True if the current input source (the result of the current input
694 699 plus prior inputs) forms a complete Python execution block. Note that
695 700 this value is also stored as a private attribute (_is_complete), so it
696 701 can be queried at any time.
697 702 """
698 703
699 704 # We must ensure all input is pure unicode
700 705 lines = cast_unicode(lines, self.encoding)
701 706 # ''.splitlines() --> [], but we need to push the empty line to transformers
702 707 lines_list = lines.splitlines()
703 708 if not lines_list:
704 709 lines_list = ['']
705 710
706 711 # Store raw source before applying any transformations to it. Note
707 712 # that this must be done *after* the reset() call that would otherwise
708 713 # flush the buffer.
709 714 self._store(lines, self._buffer_raw, 'source_raw')
710 715
711 716 transformed_lines_list = []
712 717 for line in lines_list:
713 718 transformed = self._transform_line(line)
714 719 if transformed is not None:
715 720 transformed_lines_list.append(transformed)
716 721
717 722 if transformed_lines_list:
718 723 transformed_lines = '\n'.join(transformed_lines_list)
719 724 return super(IPythonInputSplitter, self).push(transformed_lines)
720 725 else:
721 726 # Got nothing back from transformers - they must be waiting for
722 727 # more input.
723 728 return False
724 729
725 730 def _transform_line(self, line):
726 731 """Push a line of input code through the various transformers.
727 732
728 733 Returns any output from the transformers, or None if a transformer
729 734 is accumulating lines.
730 735
731 736 Sets self.transformer_accumulating as a side effect.
732 737 """
733 738 def _accumulating(dbg):
734 739 #print(dbg)
735 740 self.transformer_accumulating = True
736 741 return None
737 742
738 743 for transformer in self.physical_line_transforms:
739 744 line = transformer.push(line)
740 745 if line is None:
741 746 return _accumulating(transformer)
742 747
743 748 if not self.within_python_line:
744 749 line = self.assemble_logical_lines.push(line)
745 750 if line is None:
746 751 return _accumulating('acc logical line')
747 752
748 753 for transformer in self.logical_line_transforms:
749 754 line = transformer.push(line)
750 755 if line is None:
751 756 return _accumulating(transformer)
752 757
753 758 line = self.assemble_python_lines.push(line)
754 759 if line is None:
755 760 self.within_python_line = True
756 761 return _accumulating('acc python line')
757 762 else:
758 763 self.within_python_line = False
759 764
760 765 for transformer in self.python_line_transforms:
761 766 line = transformer.push(line)
762 767 if line is None:
763 768 return _accumulating(transformer)
764 769
765 770 #print("transformers clear") #debug
766 771 self.transformer_accumulating = False
767 772 return line
768 773
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,189 +1,195 b''
1 """Tests for the token-based transformers in IPython.core.inputtransformer2
2
3 Line-based transformers are the simpler ones; token-based transformers are
4 more complex.
5 """
6
1 7 import nose.tools as nt
2 8
3 9 from IPython.core import inputtransformer2 as ipt2
4 10 from IPython.core.inputtransformer2 import make_tokens_by_line
5 11
6 12 MULTILINE_MAGIC = ("""\
7 13 a = f()
8 14 %foo \\
9 15 bar
10 16 g()
11 17 """.splitlines(keepends=True), (2, 0), """\
12 18 a = f()
13 19 get_ipython().run_line_magic('foo', ' bar')
14 20 g()
15 21 """.splitlines(keepends=True))
16 22
17 23 INDENTED_MAGIC = ("""\
18 24 for a in range(5):
19 25 %ls
20 26 """.splitlines(keepends=True), (2, 4), """\
21 27 for a in range(5):
22 28 get_ipython().run_line_magic('ls', '')
23 29 """.splitlines(keepends=True))
24 30
25 31 MULTILINE_MAGIC_ASSIGN = ("""\
26 32 a = f()
27 33 b = %foo \\
28 34 bar
29 35 g()
30 36 """.splitlines(keepends=True), (2, 4), """\
31 37 a = f()
32 38 b = get_ipython().run_line_magic('foo', ' bar')
33 39 g()
34 40 """.splitlines(keepends=True))
35 41
36 42 MULTILINE_SYSTEM_ASSIGN = ("""\
37 43 a = f()
38 44 b = !foo \\
39 45 bar
40 46 g()
41 47 """.splitlines(keepends=True), (2, 4), """\
42 48 a = f()
43 49 b = get_ipython().getoutput('foo bar')
44 50 g()
45 51 """.splitlines(keepends=True))
46 52
47 53 AUTOCALL_QUOTE = (
48 54 [",f 1 2 3\n"], (1, 0),
49 55 ['f("1", "2", "3")\n']
50 56 )
51 57
52 58 AUTOCALL_QUOTE2 = (
53 59 [";f 1 2 3\n"], (1, 0),
54 60 ['f("1 2 3")\n']
55 61 )
56 62
57 63 AUTOCALL_PAREN = (
58 64 ["/f 1 2 3\n"], (1, 0),
59 65 ['f(1, 2, 3)\n']
60 66 )
61 67
62 68 SIMPLE_HELP = (
63 69 ["foo?\n"], (1, 0),
64 70 ["get_ipython().run_line_magic('pinfo', 'foo')\n"]
65 71 )
66 72
67 73 DETAILED_HELP = (
68 74 ["foo??\n"], (1, 0),
69 75 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"]
70 76 )
71 77
72 78 MAGIC_HELP = (
73 79 ["%foo?\n"], (1, 0),
74 80 ["get_ipython().run_line_magic('pinfo', '%foo')\n"]
75 81 )
76 82
77 83 HELP_IN_EXPR = (
78 84 ["a = b + c?\n"], (1, 0),
79 85 ["get_ipython().set_next_input('a = b + c');"
80 86 "get_ipython().run_line_magic('pinfo', 'c')\n"]
81 87 )
82 88
83 89 HELP_CONTINUED_LINE = ("""\
84 90 a = \\
85 91 zip?
86 92 """.splitlines(keepends=True), (1, 0),
87 93 [r"get_ipython().set_next_input('a = \\\nzip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
88 94 )
89 95
90 96 HELP_MULTILINE = ("""\
91 97 (a,
92 98 b) = zip?
93 99 """.splitlines(keepends=True), (1, 0),
94 100 [r"get_ipython().set_next_input('(a,\nb) = zip');get_ipython().run_line_magic('pinfo', 'zip')" + "\n"]
95 101 )
96 102
97 103 def check_find(transformer, case, match=True):
98 104 sample, expected_start, _ = case
99 105 tbl = make_tokens_by_line(sample)
100 106 res = transformer.find(tbl)
101 107 if match:
102 108 # start_line is stored 0-indexed, expected values are 1-indexed
103 109 nt.assert_equal((res.start_line+1, res.start_col), expected_start)
104 110 return res
105 111 else:
106 112 nt.assert_is(res, None)
107 113
108 114 def check_transform(transformer_cls, case):
109 115 lines, start, expected = case
110 116 transformer = transformer_cls(start)
111 117 nt.assert_equal(transformer.transform(lines), expected)
112 118
113 119 def test_continued_line():
114 120 lines = MULTILINE_MAGIC_ASSIGN[0]
115 121 nt.assert_equal(ipt2.find_end_of_continued_line(lines, 1), 2)
116 122
117 123 nt.assert_equal(ipt2.assemble_continued_line(lines, (1, 5), 2), "foo bar")
118 124
119 125 def test_find_assign_magic():
120 126 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
121 127 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
122 128
123 129 def test_transform_assign_magic():
124 130 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
125 131
126 132 def test_find_assign_system():
127 133 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
128 134 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
129 135 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
130 136 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
131 137
132 138 def test_transform_assign_system():
133 139 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
134 140
135 141 def test_find_magic_escape():
136 142 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
137 143 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
138 144 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
139 145
140 146 def test_transform_magic_escape():
141 147 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
142 148 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
143 149
144 150 def test_find_autocalls():
145 151 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
146 152 print("Testing %r" % case[0])
147 153 check_find(ipt2.EscapedCommand, case)
148 154
149 155 def test_transform_autocall():
150 156 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
151 157 print("Testing %r" % case[0])
152 158 check_transform(ipt2.EscapedCommand, case)
153 159
154 160 def test_find_help():
155 161 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
156 162 check_find(ipt2.HelpEnd, case)
157 163
158 164 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
159 165 nt.assert_equal(tf.q_line, 1)
160 166 nt.assert_equal(tf.q_col, 3)
161 167
162 168 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
163 169 nt.assert_equal(tf.q_line, 1)
164 170 nt.assert_equal(tf.q_col, 8)
165 171
166 172 # ? in a comment does not trigger help
167 173 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
168 174 # Nor in a string
169 175 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
170 176
171 177 def test_transform_help():
172 178 tf = ipt2.HelpEnd((1, 0), (1, 9))
173 179 nt.assert_equal(tf.transform(HELP_IN_EXPR[0]), HELP_IN_EXPR[2])
174 180
175 181 tf = ipt2.HelpEnd((1, 0), (2, 3))
176 182 nt.assert_equal(tf.transform(HELP_CONTINUED_LINE[0]), HELP_CONTINUED_LINE[2])
177 183
178 184 tf = ipt2.HelpEnd((1, 0), (2, 8))
179 185 nt.assert_equal(tf.transform(HELP_MULTILINE[0]), HELP_MULTILINE[2])
180 186
181 187 def test_check_complete():
182 188 cc = ipt2.TransformerManager().check_complete
183 189 nt.assert_equal(cc("a = 1"), ('complete', None))
184 190 nt.assert_equal(cc("for a in range(5):"), ('incomplete', 4))
185 191 nt.assert_equal(cc("raise = 2"), ('invalid', None))
186 192 nt.assert_equal(cc("a = [1,\n2,"), ('incomplete', 0))
187 193 nt.assert_equal(cc("a = '''\n hi"), ('incomplete', 3))
188 194 nt.assert_equal(cc("def a():\n x=1\n global x"), ('invalid', None))
189 195 nt.assert_equal(cc("a \\ "), ('invalid', None)) # Nothing allowed after backslash
General Comments 0
You need to be logged in to leave comments. Login now