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