##// END OF EJS Templates
SyntaxWarning in compile indicates invalid input
Min RK -
Show More
@@ -1,679 +1,682 b''
1 1 """Input handling and transformation machinery.
2 2
3 3 The first class in this module, :class:`InputSplitter`, is designed to tell when
4 4 input from a line-oriented frontend is complete and should be executed, and when
5 5 the user should be prompted for another line of code instead. The name 'input
6 6 splitter' is largely for historical reasons.
7 7
8 8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
9 9 with full support for the extended IPython syntax (magics, system calls, etc).
10 10 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
11 11 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
12 12 and stores the results.
13 13
14 14 For more details, see the class docstrings below.
15 15 """
16 16
17 17 # Copyright (c) IPython Development Team.
18 18 # Distributed under the terms of the Modified BSD License.
19 19 import ast
20 20 import codeop
21 21 import re
22 22 import sys
23 import warnings
23 24
24 25 from IPython.utils.py3compat import cast_unicode
25 26 from IPython.core.inputtransformer import (leading_indent,
26 27 classic_prompt,
27 28 ipy_prompt,
28 29 strip_encoding_cookie,
29 30 cellmagic,
30 31 assemble_logical_lines,
31 32 help_end,
32 33 escaped_commands,
33 34 assign_from_magic,
34 35 assign_from_system,
35 36 assemble_python_lines,
36 37 )
37 38
38 39 # These are available in this module for backwards compatibility.
39 40 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
40 41 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
41 42 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
42 43
43 44 #-----------------------------------------------------------------------------
44 45 # Utilities
45 46 #-----------------------------------------------------------------------------
46 47
47 48 # FIXME: These are general-purpose utilities that later can be moved to the
48 49 # general ward. Kept here for now because we're being very strict about test
49 50 # coverage with this code, and this lets us ensure that we keep 100% coverage
50 51 # while developing.
51 52
52 53 # compiled regexps for autoindent management
53 54 dedent_re = re.compile('|'.join([
54 55 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
55 56 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
56 57 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
57 58 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
58 59 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
59 60 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
60 61 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
61 62 ]))
62 63 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
63 64
64 65 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
65 66 # before pure comments
66 67 comment_line_re = re.compile('^\s*\#')
67 68
68 69
69 70 def num_ini_spaces(s):
70 71 """Return the number of initial spaces in a string.
71 72
72 73 Note that tabs are counted as a single space. For now, we do *not* support
73 74 mixing of tabs and spaces in the user's input.
74 75
75 76 Parameters
76 77 ----------
77 78 s : string
78 79
79 80 Returns
80 81 -------
81 82 n : int
82 83 """
83 84
84 85 ini_spaces = ini_spaces_re.match(s)
85 86 if ini_spaces:
86 87 return ini_spaces.end()
87 88 else:
88 89 return 0
89 90
90 91 def last_blank(src):
91 92 """Determine if the input source ends in a blank.
92 93
93 94 A blank is either a newline or a line consisting of whitespace.
94 95
95 96 Parameters
96 97 ----------
97 98 src : string
98 99 A single or multiline string.
99 100 """
100 101 if not src: return False
101 102 ll = src.splitlines()[-1]
102 103 return (ll == '') or ll.isspace()
103 104
104 105
105 106 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
106 107 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
107 108
108 109 def last_two_blanks(src):
109 110 """Determine if the input source ends in two blanks.
110 111
111 112 A blank is either a newline or a line consisting of whitespace.
112 113
113 114 Parameters
114 115 ----------
115 116 src : string
116 117 A single or multiline string.
117 118 """
118 119 if not src: return False
119 120 # The logic here is tricky: I couldn't get a regexp to work and pass all
120 121 # the tests, so I took a different approach: split the source by lines,
121 122 # grab the last two and prepend '###\n' as a stand-in for whatever was in
122 123 # the body before the last two lines. Then, with that structure, it's
123 124 # possible to analyze with two regexps. Not the most elegant solution, but
124 125 # it works. If anyone tries to change this logic, make sure to validate
125 126 # the whole test suite first!
126 127 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
127 128 return (bool(last_two_blanks_re.match(new_src)) or
128 129 bool(last_two_blanks_re2.match(new_src)) )
129 130
130 131
131 132 def remove_comments(src):
132 133 """Remove all comments from input source.
133 134
134 135 Note: comments are NOT recognized inside of strings!
135 136
136 137 Parameters
137 138 ----------
138 139 src : string
139 140 A single or multiline input string.
140 141
141 142 Returns
142 143 -------
143 144 String with all Python comments removed.
144 145 """
145 146
146 147 return re.sub('#.*', '', src)
147 148
148 149
149 150 def get_input_encoding():
150 151 """Return the default standard input encoding.
151 152
152 153 If sys.stdin has no encoding, 'ascii' is returned."""
153 154 # There are strange environments for which sys.stdin.encoding is None. We
154 155 # ensure that a valid encoding is returned.
155 156 encoding = getattr(sys.stdin, 'encoding', None)
156 157 if encoding is None:
157 158 encoding = 'ascii'
158 159 return encoding
159 160
160 161 #-----------------------------------------------------------------------------
161 162 # Classes and functions for normal Python syntax handling
162 163 #-----------------------------------------------------------------------------
163 164
164 165 class InputSplitter(object):
165 166 r"""An object that can accumulate lines of Python source before execution.
166 167
167 168 This object is designed to be fed python source line-by-line, using
168 169 :meth:`push`. It will return on each push whether the currently pushed
169 170 code could be executed already. In addition, it provides a method called
170 171 :meth:`push_accepts_more` that can be used to query whether more input
171 172 can be pushed into a single interactive block.
172 173
173 174 This is a simple example of how an interactive terminal-based client can use
174 175 this tool::
175 176
176 177 isp = InputSplitter()
177 178 while isp.push_accepts_more():
178 179 indent = ' '*isp.indent_spaces
179 180 prompt = '>>> ' + indent
180 181 line = indent + raw_input(prompt)
181 182 isp.push(line)
182 183 print 'Input source was:\n', isp.source_reset(),
183 184 """
184 185 # Number of spaces of indentation computed from input that has been pushed
185 186 # so far. This is the attributes callers should query to get the current
186 187 # indentation level, in order to provide auto-indent facilities.
187 188 indent_spaces = 0
188 189 # String, indicating the default input encoding. It is computed by default
189 190 # at initialization time via get_input_encoding(), but it can be reset by a
190 191 # client with specific knowledge of the encoding.
191 192 encoding = ''
192 193 # String where the current full source input is stored, properly encoded.
193 194 # Reading this attribute is the normal way of querying the currently pushed
194 195 # source code, that has been properly encoded.
195 196 source = ''
196 197 # Code object corresponding to the current source. It is automatically
197 198 # synced to the source, so it can be queried at any time to obtain the code
198 199 # object; it will be None if the source doesn't compile to valid Python.
199 200 code = None
200 201
201 202 # Private attributes
202 203
203 204 # List with lines of input accumulated so far
204 205 _buffer = None
205 206 # Command compiler
206 207 _compile = None
207 208 # Mark when input has changed indentation all the way back to flush-left
208 209 _full_dedent = False
209 210 # Boolean indicating whether the current block is complete
210 211 _is_complete = None
211 212 # Boolean indicating whether the current block has an unrecoverable syntax error
212 213 _is_invalid = False
213 214
214 215 def __init__(self):
215 216 """Create a new InputSplitter instance.
216 217 """
217 218 self._buffer = []
218 219 self._compile = codeop.CommandCompiler()
219 220 self.encoding = get_input_encoding()
220 221
221 222 def reset(self):
222 223 """Reset the input buffer and associated state."""
223 224 self.indent_spaces = 0
224 225 self._buffer[:] = []
225 226 self.source = ''
226 227 self.code = None
227 228 self._is_complete = False
228 229 self._is_invalid = False
229 230 self._full_dedent = False
230 231
231 232 def source_reset(self):
232 233 """Return the input source and perform a full reset.
233 234 """
234 235 out = self.source
235 236 self.reset()
236 237 return out
237 238
238 239 def check_complete(self, source):
239 240 """Return whether a block of code is ready to execute, or should be continued
240 241
241 242 This is a non-stateful API, and will reset the state of this InputSplitter.
242 243
243 244 Parameters
244 245 ----------
245 246 source : string
246 247 Python input code, which can be multiline.
247 248
248 249 Returns
249 250 -------
250 251 status : str
251 252 One of 'complete', 'incomplete', or 'invalid' if source is not a
252 253 prefix of valid code.
253 254 indent_spaces : int or None
254 255 The number of spaces by which to indent the next line of code. If
255 256 status is not 'incomplete', this is None.
256 257 """
257 258 self.reset()
258 259 try:
259 260 self.push(source)
260 261 except SyntaxError:
261 262 # Transformers in IPythonInputSplitter can raise SyntaxError,
262 263 # which push() will not catch.
263 264 return 'invalid', None
264 265 else:
265 266 if self._is_invalid:
266 267 return 'invalid', None
267 268 elif self.push_accepts_more():
268 269 return 'incomplete', self.indent_spaces
269 270 else:
270 271 return 'complete', None
271 272 finally:
272 273 self.reset()
273 274
274 275 def push(self, lines):
275 276 """Push one or more lines of input.
276 277
277 278 This stores the given lines and returns a status code indicating
278 279 whether the code forms a complete Python block or not.
279 280
280 281 Any exceptions generated in compilation are swallowed, but if an
281 282 exception was produced, the method returns True.
282 283
283 284 Parameters
284 285 ----------
285 286 lines : string
286 287 One or more lines of Python input.
287 288
288 289 Returns
289 290 -------
290 291 is_complete : boolean
291 292 True if the current input source (the result of the current input
292 293 plus prior inputs) forms a complete Python execution block. Note that
293 294 this value is also stored as a private attribute (``_is_complete``), so it
294 295 can be queried at any time.
295 296 """
296 297 self._store(lines)
297 298 source = self.source
298 299
299 300 # Before calling _compile(), reset the code object to None so that if an
300 301 # exception is raised in compilation, we don't mislead by having
301 302 # inconsistent code/source attributes.
302 303 self.code, self._is_complete = None, None
303 304 self._is_invalid = False
304 305
305 306 # Honor termination lines properly
306 307 if source.endswith('\\\n'):
307 308 return False
308 309
309 310 self._update_indent(lines)
310 311 try:
312 with warnings.catch_warnings():
313 warnings.simplefilter('error', SyntaxWarning)
311 314 self.code = self._compile(source, symbol="exec")
312 315 # Invalid syntax can produce any of a number of different errors from
313 316 # inside the compiler, so we have to catch them all. Syntax errors
314 317 # immediately produce a 'ready' block, so the invalid Python can be
315 318 # sent to the kernel for evaluation with possible ipython
316 319 # special-syntax conversion.
317 320 except (SyntaxError, OverflowError, ValueError, TypeError,
318 MemoryError):
321 MemoryError, SyntaxWarning):
319 322 self._is_complete = True
320 323 self._is_invalid = True
321 324 else:
322 325 # Compilation didn't produce any exceptions (though it may not have
323 326 # given a complete code object)
324 327 self._is_complete = self.code is not None
325 328
326 329 return self._is_complete
327 330
328 331 def push_accepts_more(self):
329 332 """Return whether a block of interactive input can accept more input.
330 333
331 334 This method is meant to be used by line-oriented frontends, who need to
332 335 guess whether a block is complete or not based solely on prior and
333 336 current input lines. The InputSplitter considers it has a complete
334 337 interactive block and will not accept more input when either:
335 338
336 339 * A SyntaxError is raised
337 340
338 341 * The code is complete and consists of a single line or a single
339 342 non-compound statement
340 343
341 344 * The code is complete and has a blank line at the end
342 345
343 346 If the current input produces a syntax error, this method immediately
344 347 returns False but does *not* raise the syntax error exception, as
345 348 typically clients will want to send invalid syntax to an execution
346 349 backend which might convert the invalid syntax into valid Python via
347 350 one of the dynamic IPython mechanisms.
348 351 """
349 352
350 353 # With incomplete input, unconditionally accept more
351 354 # A syntax error also sets _is_complete to True - see push()
352 355 if not self._is_complete:
353 356 #print("Not complete") # debug
354 357 return True
355 358
356 359 # The user can make any (complete) input execute by leaving a blank line
357 360 last_line = self.source.splitlines()[-1]
358 361 if (not last_line) or last_line.isspace():
359 362 #print("Blank line") # debug
360 363 return False
361 364
362 365 # If there's just a single line or AST node, and we're flush left, as is
363 366 # the case after a simple statement such as 'a=1', we want to execute it
364 367 # straight away.
365 368 if self.indent_spaces==0:
366 369 if len(self.source.splitlines()) <= 1:
367 370 return False
368 371
369 372 try:
370 373 code_ast = ast.parse(u''.join(self._buffer))
371 374 except Exception:
372 375 #print("Can't parse AST") # debug
373 376 return False
374 377 else:
375 378 if len(code_ast.body) == 1 and \
376 379 not hasattr(code_ast.body[0], 'body'):
377 380 #print("Simple statement") # debug
378 381 return False
379 382
380 383 # General fallback - accept more code
381 384 return True
382 385
383 386 #------------------------------------------------------------------------
384 387 # Private interface
385 388 #------------------------------------------------------------------------
386 389
387 390 def _find_indent(self, line):
388 391 """Compute the new indentation level for a single line.
389 392
390 393 Parameters
391 394 ----------
392 395 line : str
393 396 A single new line of non-whitespace, non-comment Python input.
394 397
395 398 Returns
396 399 -------
397 400 indent_spaces : int
398 401 New value for the indent level (it may be equal to self.indent_spaces
399 402 if indentation doesn't change.
400 403
401 404 full_dedent : boolean
402 405 Whether the new line causes a full flush-left dedent.
403 406 """
404 407 indent_spaces = self.indent_spaces
405 408 full_dedent = self._full_dedent
406 409
407 410 inisp = num_ini_spaces(line)
408 411 if inisp < indent_spaces:
409 412 indent_spaces = inisp
410 413 if indent_spaces <= 0:
411 414 #print 'Full dedent in text',self.source # dbg
412 415 full_dedent = True
413 416
414 417 if line.rstrip()[-1] == ':':
415 418 indent_spaces += 4
416 419 elif dedent_re.match(line):
417 420 indent_spaces -= 4
418 421 if indent_spaces <= 0:
419 422 full_dedent = True
420 423
421 424 # Safety
422 425 if indent_spaces < 0:
423 426 indent_spaces = 0
424 427 #print 'safety' # dbg
425 428
426 429 return indent_spaces, full_dedent
427 430
428 431 def _update_indent(self, lines):
429 432 for line in remove_comments(lines).splitlines():
430 433 if line and not line.isspace():
431 434 self.indent_spaces, self._full_dedent = self._find_indent(line)
432 435
433 436 def _store(self, lines, buffer=None, store='source'):
434 437 """Store one or more lines of input.
435 438
436 439 If input lines are not newline-terminated, a newline is automatically
437 440 appended."""
438 441
439 442 if buffer is None:
440 443 buffer = self._buffer
441 444
442 445 if lines.endswith('\n'):
443 446 buffer.append(lines)
444 447 else:
445 448 buffer.append(lines+'\n')
446 449 setattr(self, store, self._set_source(buffer))
447 450
448 451 def _set_source(self, buffer):
449 452 return u''.join(buffer)
450 453
451 454
452 455 class IPythonInputSplitter(InputSplitter):
453 456 """An input splitter that recognizes all of IPython's special syntax."""
454 457
455 458 # String with raw, untransformed input.
456 459 source_raw = ''
457 460
458 461 # Flag to track when a transformer has stored input that it hasn't given
459 462 # back yet.
460 463 transformer_accumulating = False
461 464
462 465 # Flag to track when assemble_python_lines has stored input that it hasn't
463 466 # given back yet.
464 467 within_python_line = False
465 468
466 469 # Private attributes
467 470
468 471 # List with lines of raw input accumulated so far.
469 472 _buffer_raw = None
470 473
471 474 def __init__(self, line_input_checker=True, physical_line_transforms=None,
472 475 logical_line_transforms=None, python_line_transforms=None):
473 476 super(IPythonInputSplitter, self).__init__()
474 477 self._buffer_raw = []
475 478 self._validate = True
476 479
477 480 if physical_line_transforms is not None:
478 481 self.physical_line_transforms = physical_line_transforms
479 482 else:
480 483 self.physical_line_transforms = [
481 484 leading_indent(),
482 485 classic_prompt(),
483 486 ipy_prompt(),
484 487 strip_encoding_cookie(),
485 488 cellmagic(end_on_blank_line=line_input_checker),
486 489 ]
487 490
488 491 self.assemble_logical_lines = assemble_logical_lines()
489 492 if logical_line_transforms is not None:
490 493 self.logical_line_transforms = logical_line_transforms
491 494 else:
492 495 self.logical_line_transforms = [
493 496 help_end(),
494 497 escaped_commands(),
495 498 assign_from_magic(),
496 499 assign_from_system(),
497 500 ]
498 501
499 502 self.assemble_python_lines = assemble_python_lines()
500 503 if python_line_transforms is not None:
501 504 self.python_line_transforms = python_line_transforms
502 505 else:
503 506 # We don't use any of these at present
504 507 self.python_line_transforms = []
505 508
506 509 @property
507 510 def transforms(self):
508 511 "Quick access to all transformers."
509 512 return self.physical_line_transforms + \
510 513 [self.assemble_logical_lines] + self.logical_line_transforms + \
511 514 [self.assemble_python_lines] + self.python_line_transforms
512 515
513 516 @property
514 517 def transforms_in_use(self):
515 518 """Transformers, excluding logical line transformers if we're in a
516 519 Python line."""
517 520 t = self.physical_line_transforms[:]
518 521 if not self.within_python_line:
519 522 t += [self.assemble_logical_lines] + self.logical_line_transforms
520 523 return t + [self.assemble_python_lines] + self.python_line_transforms
521 524
522 525 def reset(self):
523 526 """Reset the input buffer and associated state."""
524 527 super(IPythonInputSplitter, self).reset()
525 528 self._buffer_raw[:] = []
526 529 self.source_raw = ''
527 530 self.transformer_accumulating = False
528 531 self.within_python_line = False
529 532
530 533 for t in self.transforms:
531 534 try:
532 535 t.reset()
533 536 except SyntaxError:
534 537 # Nothing that calls reset() expects to handle transformer
535 538 # errors
536 539 pass
537 540
538 541 def flush_transformers(self):
539 542 def _flush(transform, outs):
540 543 """yield transformed lines
541 544
542 545 always strings, never None
543 546
544 547 transform: the current transform
545 548 outs: an iterable of previously transformed inputs.
546 549 Each may be multiline, which will be passed
547 550 one line at a time to transform.
548 551 """
549 552 for out in outs:
550 553 for line in out.splitlines():
551 554 # push one line at a time
552 555 tmp = transform.push(line)
553 556 if tmp is not None:
554 557 yield tmp
555 558
556 559 # reset the transform
557 560 tmp = transform.reset()
558 561 if tmp is not None:
559 562 yield tmp
560 563
561 564 out = []
562 565 for t in self.transforms_in_use:
563 566 out = _flush(t, out)
564 567
565 568 out = list(out)
566 569 if out:
567 570 self._store('\n'.join(out))
568 571
569 572 def raw_reset(self):
570 573 """Return raw input only and perform a full reset.
571 574 """
572 575 out = self.source_raw
573 576 self.reset()
574 577 return out
575 578
576 579 def source_reset(self):
577 580 try:
578 581 self.flush_transformers()
579 582 return self.source
580 583 finally:
581 584 self.reset()
582 585
583 586 def push_accepts_more(self):
584 587 if self.transformer_accumulating:
585 588 return True
586 589 else:
587 590 return super(IPythonInputSplitter, self).push_accepts_more()
588 591
589 592 def transform_cell(self, cell):
590 593 """Process and translate a cell of input.
591 594 """
592 595 self.reset()
593 596 try:
594 597 self.push(cell)
595 598 self.flush_transformers()
596 599 return self.source
597 600 finally:
598 601 self.reset()
599 602
600 603 def push(self, lines):
601 604 """Push one or more lines of IPython input.
602 605
603 606 This stores the given lines and returns a status code indicating
604 607 whether the code forms a complete Python block or not, after processing
605 608 all input lines for special IPython syntax.
606 609
607 610 Any exceptions generated in compilation are swallowed, but if an
608 611 exception was produced, the method returns True.
609 612
610 613 Parameters
611 614 ----------
612 615 lines : string
613 616 One or more lines of Python input.
614 617
615 618 Returns
616 619 -------
617 620 is_complete : boolean
618 621 True if the current input source (the result of the current input
619 622 plus prior inputs) forms a complete Python execution block. Note that
620 623 this value is also stored as a private attribute (_is_complete), so it
621 624 can be queried at any time.
622 625 """
623 626
624 627 # We must ensure all input is pure unicode
625 628 lines = cast_unicode(lines, self.encoding)
626 629
627 630 # ''.splitlines() --> [], but we need to push the empty line to transformers
628 631 lines_list = lines.splitlines()
629 632 if not lines_list:
630 633 lines_list = ['']
631 634
632 635 # Store raw source before applying any transformations to it. Note
633 636 # that this must be done *after* the reset() call that would otherwise
634 637 # flush the buffer.
635 638 self._store(lines, self._buffer_raw, 'source_raw')
636 639
637 640 for line in lines_list:
638 641 out = self.push_line(line)
639 642
640 643 return out
641 644
642 645 def push_line(self, line):
643 646 buf = self._buffer
644 647
645 648 def _accumulating(dbg):
646 649 #print(dbg)
647 650 self.transformer_accumulating = True
648 651 return False
649 652
650 653 for transformer in self.physical_line_transforms:
651 654 line = transformer.push(line)
652 655 if line is None:
653 656 return _accumulating(transformer)
654 657
655 658 if not self.within_python_line:
656 659 line = self.assemble_logical_lines.push(line)
657 660 if line is None:
658 661 return _accumulating('acc logical line')
659 662
660 663 for transformer in self.logical_line_transforms:
661 664 line = transformer.push(line)
662 665 if line is None:
663 666 return _accumulating(transformer)
664 667
665 668 line = self.assemble_python_lines.push(line)
666 669 if line is None:
667 670 self.within_python_line = True
668 671 return _accumulating('acc python line')
669 672 else:
670 673 self.within_python_line = False
671 674
672 675 for transformer in self.python_line_transforms:
673 676 line = transformer.push(line)
674 677 if line is None:
675 678 return _accumulating(transformer)
676 679
677 680 #print("transformers clear") #debug
678 681 self.transformer_accumulating = False
679 682 return super(IPythonInputSplitter, self).push(line)
@@ -1,604 +1,605 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the inputsplitter module."""
3 3
4 4 from __future__ import print_function
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import unittest
10 10 import sys
11 11
12 12 import nose.tools as nt
13 13
14 14 from IPython.core import inputsplitter as isp
15 15 from IPython.core.inputtransformer import InputTransformer
16 16 from IPython.core.tests.test_inputtransformer import syntax, syntax_ml
17 17 from IPython.testing import tools as tt
18 18 from IPython.utils import py3compat
19 19 from IPython.utils.py3compat import string_types, input
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Semi-complete examples (also used as tests)
23 23 #-----------------------------------------------------------------------------
24 24
25 25 # Note: at the bottom, there's a slightly more complete version of this that
26 26 # can be useful during development of code here.
27 27
28 28 def mini_interactive_loop(input_func):
29 29 """Minimal example of the logic of an interactive interpreter loop.
30 30
31 31 This serves as an example, and it is used by the test system with a fake
32 32 raw_input that simulates interactive input."""
33 33
34 34 from IPython.core.inputsplitter import InputSplitter
35 35
36 36 isp = InputSplitter()
37 37 # In practice, this input loop would be wrapped in an outside loop to read
38 38 # input indefinitely, until some exit/quit command was issued. Here we
39 39 # only illustrate the basic inner loop.
40 40 while isp.push_accepts_more():
41 41 indent = ' '*isp.indent_spaces
42 42 prompt = '>>> ' + indent
43 43 line = indent + input_func(prompt)
44 44 isp.push(line)
45 45
46 46 # Here we just return input so we can use it in a test suite, but a real
47 47 # interpreter would instead send it for execution somewhere.
48 48 src = isp.source_reset()
49 49 #print 'Input source was:\n', src # dbg
50 50 return src
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # Test utilities, just for local use
54 54 #-----------------------------------------------------------------------------
55 55
56 56 def assemble(block):
57 57 """Assemble a block into multi-line sub-blocks."""
58 58 return ['\n'.join(sub_block)+'\n' for sub_block in block]
59 59
60 60
61 61 def pseudo_input(lines):
62 62 """Return a function that acts like raw_input but feeds the input list."""
63 63 ilines = iter(lines)
64 64 def raw_in(prompt):
65 65 try:
66 66 return next(ilines)
67 67 except StopIteration:
68 68 return ''
69 69 return raw_in
70 70
71 71 #-----------------------------------------------------------------------------
72 72 # Tests
73 73 #-----------------------------------------------------------------------------
74 74 def test_spaces():
75 75 tests = [('', 0),
76 76 (' ', 1),
77 77 ('\n', 0),
78 78 (' \n', 1),
79 79 ('x', 0),
80 80 (' x', 1),
81 81 (' x',2),
82 82 (' x',4),
83 83 # Note: tabs are counted as a single whitespace!
84 84 ('\tx', 1),
85 85 ('\t x', 2),
86 86 ]
87 87 tt.check_pairs(isp.num_ini_spaces, tests)
88 88
89 89
90 90 def test_remove_comments():
91 91 tests = [('text', 'text'),
92 92 ('text # comment', 'text '),
93 93 ('text # comment\n', 'text \n'),
94 94 ('text # comment \n', 'text \n'),
95 95 ('line # c \nline\n','line \nline\n'),
96 96 ('line # c \nline#c2 \nline\nline #c\n\n',
97 97 'line \nline\nline\nline \n\n'),
98 98 ]
99 99 tt.check_pairs(isp.remove_comments, tests)
100 100
101 101
102 102 def test_get_input_encoding():
103 103 encoding = isp.get_input_encoding()
104 104 nt.assert_true(isinstance(encoding, string_types))
105 105 # simple-minded check that at least encoding a simple string works with the
106 106 # encoding we got.
107 107 nt.assert_equal(u'test'.encode(encoding), b'test')
108 108
109 109
110 110 class NoInputEncodingTestCase(unittest.TestCase):
111 111 def setUp(self):
112 112 self.old_stdin = sys.stdin
113 113 class X: pass
114 114 fake_stdin = X()
115 115 sys.stdin = fake_stdin
116 116
117 117 def test(self):
118 118 # Verify that if sys.stdin has no 'encoding' attribute we do the right
119 119 # thing
120 120 enc = isp.get_input_encoding()
121 121 self.assertEqual(enc, 'ascii')
122 122
123 123 def tearDown(self):
124 124 sys.stdin = self.old_stdin
125 125
126 126
127 127 class InputSplitterTestCase(unittest.TestCase):
128 128 def setUp(self):
129 129 self.isp = isp.InputSplitter()
130 130
131 131 def test_reset(self):
132 132 isp = self.isp
133 133 isp.push('x=1')
134 134 isp.reset()
135 135 self.assertEqual(isp._buffer, [])
136 136 self.assertEqual(isp.indent_spaces, 0)
137 137 self.assertEqual(isp.source, '')
138 138 self.assertEqual(isp.code, None)
139 139 self.assertEqual(isp._is_complete, False)
140 140
141 141 def test_source(self):
142 142 self.isp._store('1')
143 143 self.isp._store('2')
144 144 self.assertEqual(self.isp.source, '1\n2\n')
145 145 self.assertTrue(len(self.isp._buffer)>0)
146 146 self.assertEqual(self.isp.source_reset(), '1\n2\n')
147 147 self.assertEqual(self.isp._buffer, [])
148 148 self.assertEqual(self.isp.source, '')
149 149
150 150 def test_indent(self):
151 151 isp = self.isp # shorthand
152 152 isp.push('x=1')
153 153 self.assertEqual(isp.indent_spaces, 0)
154 154 isp.push('if 1:\n x=1')
155 155 self.assertEqual(isp.indent_spaces, 4)
156 156 isp.push('y=2\n')
157 157 self.assertEqual(isp.indent_spaces, 0)
158 158
159 159 def test_indent2(self):
160 160 isp = self.isp
161 161 isp.push('if 1:')
162 162 self.assertEqual(isp.indent_spaces, 4)
163 163 isp.push(' x=1')
164 164 self.assertEqual(isp.indent_spaces, 4)
165 165 # Blank lines shouldn't change the indent level
166 166 isp.push(' '*2)
167 167 self.assertEqual(isp.indent_spaces, 4)
168 168
169 169 def test_indent3(self):
170 170 isp = self.isp
171 171 # When a multiline statement contains parens or multiline strings, we
172 172 # shouldn't get confused.
173 173 isp.push("if 1:")
174 174 isp.push(" x = (1+\n 2)")
175 175 self.assertEqual(isp.indent_spaces, 4)
176 176
177 177 def test_indent4(self):
178 178 isp = self.isp
179 179 # whitespace after ':' should not screw up indent level
180 180 isp.push('if 1: \n x=1')
181 181 self.assertEqual(isp.indent_spaces, 4)
182 182 isp.push('y=2\n')
183 183 self.assertEqual(isp.indent_spaces, 0)
184 184 isp.push('if 1:\t\n x=1')
185 185 self.assertEqual(isp.indent_spaces, 4)
186 186 isp.push('y=2\n')
187 187 self.assertEqual(isp.indent_spaces, 0)
188 188
189 189 def test_dedent_pass(self):
190 190 isp = self.isp # shorthand
191 191 # should NOT cause dedent
192 192 isp.push('if 1:\n passes = 5')
193 193 self.assertEqual(isp.indent_spaces, 4)
194 194 isp.push('if 1:\n pass')
195 195 self.assertEqual(isp.indent_spaces, 0)
196 196 isp.push('if 1:\n pass ')
197 197 self.assertEqual(isp.indent_spaces, 0)
198 198
199 199 def test_dedent_break(self):
200 200 isp = self.isp # shorthand
201 201 # should NOT cause dedent
202 202 isp.push('while 1:\n breaks = 5')
203 203 self.assertEqual(isp.indent_spaces, 4)
204 204 isp.push('while 1:\n break')
205 205 self.assertEqual(isp.indent_spaces, 0)
206 206 isp.push('while 1:\n break ')
207 207 self.assertEqual(isp.indent_spaces, 0)
208 208
209 209 def test_dedent_continue(self):
210 210 isp = self.isp # shorthand
211 211 # should NOT cause dedent
212 212 isp.push('while 1:\n continues = 5')
213 213 self.assertEqual(isp.indent_spaces, 4)
214 214 isp.push('while 1:\n continue')
215 215 self.assertEqual(isp.indent_spaces, 0)
216 216 isp.push('while 1:\n continue ')
217 217 self.assertEqual(isp.indent_spaces, 0)
218 218
219 219 def test_dedent_raise(self):
220 220 isp = self.isp # shorthand
221 221 # should NOT cause dedent
222 222 isp.push('if 1:\n raised = 4')
223 223 self.assertEqual(isp.indent_spaces, 4)
224 224 isp.push('if 1:\n raise TypeError()')
225 225 self.assertEqual(isp.indent_spaces, 0)
226 226 isp.push('if 1:\n raise')
227 227 self.assertEqual(isp.indent_spaces, 0)
228 228 isp.push('if 1:\n raise ')
229 229 self.assertEqual(isp.indent_spaces, 0)
230 230
231 231 def test_dedent_return(self):
232 232 isp = self.isp # shorthand
233 233 # should NOT cause dedent
234 234 isp.push('if 1:\n returning = 4')
235 235 self.assertEqual(isp.indent_spaces, 4)
236 236 isp.push('if 1:\n return 5 + 493')
237 237 self.assertEqual(isp.indent_spaces, 0)
238 238 isp.push('if 1:\n return')
239 239 self.assertEqual(isp.indent_spaces, 0)
240 240 isp.push('if 1:\n return ')
241 241 self.assertEqual(isp.indent_spaces, 0)
242 242 isp.push('if 1:\n return(0)')
243 243 self.assertEqual(isp.indent_spaces, 0)
244 244
245 245 def test_push(self):
246 246 isp = self.isp
247 247 self.assertTrue(isp.push('x=1'))
248 248
249 249 def test_push2(self):
250 250 isp = self.isp
251 251 self.assertFalse(isp.push('if 1:'))
252 252 for line in [' x=1', '# a comment', ' y=2']:
253 253 print(line)
254 254 self.assertTrue(isp.push(line))
255 255
256 256 def test_push3(self):
257 257 isp = self.isp
258 258 isp.push('if True:')
259 259 isp.push(' a = 1')
260 260 self.assertFalse(isp.push('b = [1,'))
261 261
262 262 def test_push_accepts_more(self):
263 263 isp = self.isp
264 264 isp.push('x=1')
265 265 self.assertFalse(isp.push_accepts_more())
266 266
267 267 def test_push_accepts_more2(self):
268 268 isp = self.isp
269 269 isp.push('if 1:')
270 270 self.assertTrue(isp.push_accepts_more())
271 271 isp.push(' x=1')
272 272 self.assertTrue(isp.push_accepts_more())
273 273 isp.push('')
274 274 self.assertFalse(isp.push_accepts_more())
275 275
276 276 def test_push_accepts_more3(self):
277 277 isp = self.isp
278 278 isp.push("x = (2+\n3)")
279 279 self.assertFalse(isp.push_accepts_more())
280 280
281 281 def test_push_accepts_more4(self):
282 282 isp = self.isp
283 283 # When a multiline statement contains parens or multiline strings, we
284 284 # shouldn't get confused.
285 285 # FIXME: we should be able to better handle de-dents in statements like
286 286 # multiline strings and multiline expressions (continued with \ or
287 287 # parens). Right now we aren't handling the indentation tracking quite
288 288 # correctly with this, though in practice it may not be too much of a
289 289 # problem. We'll need to see.
290 290 isp.push("if 1:")
291 291 isp.push(" x = (2+")
292 292 isp.push(" 3)")
293 293 self.assertTrue(isp.push_accepts_more())
294 294 isp.push(" y = 3")
295 295 self.assertTrue(isp.push_accepts_more())
296 296 isp.push('')
297 297 self.assertFalse(isp.push_accepts_more())
298 298
299 299 def test_push_accepts_more5(self):
300 300 isp = self.isp
301 301 isp.push('try:')
302 302 isp.push(' a = 5')
303 303 isp.push('except:')
304 304 isp.push(' raise')
305 305 # We want to be able to add an else: block at this point, so it should
306 306 # wait for a blank line.
307 307 self.assertTrue(isp.push_accepts_more())
308 308
309 309 def test_continuation(self):
310 310 isp = self.isp
311 311 isp.push("import os, \\")
312 312 self.assertTrue(isp.push_accepts_more())
313 313 isp.push("sys")
314 314 self.assertFalse(isp.push_accepts_more())
315 315
316 316 def test_syntax_error(self):
317 317 isp = self.isp
318 318 # Syntax errors immediately produce a 'ready' block, so the invalid
319 319 # Python can be sent to the kernel for evaluation with possible ipython
320 320 # special-syntax conversion.
321 321 isp.push('run foo')
322 322 self.assertFalse(isp.push_accepts_more())
323 323
324 324 def test_unicode(self):
325 325 self.isp.push(u"PΓ©rez")
326 326 self.isp.push(u'\xc3\xa9')
327 327 self.isp.push(u"u'\xc3\xa9'")
328 328
329 329 def test_line_continuation(self):
330 330 """ Test issue #2108."""
331 331 isp = self.isp
332 332 # A blank line after a line continuation should not accept more
333 333 isp.push("1 \\\n\n")
334 334 self.assertFalse(isp.push_accepts_more())
335 335 # Whitespace after a \ is a SyntaxError. The only way to test that
336 336 # here is to test that push doesn't accept more (as with
337 337 # test_syntax_error() above).
338 338 isp.push(r"1 \ ")
339 339 self.assertFalse(isp.push_accepts_more())
340 340 # Even if the line is continuable (c.f. the regular Python
341 341 # interpreter)
342 342 isp.push(r"(1 \ ")
343 343 self.assertFalse(isp.push_accepts_more())
344 344
345 345 def test_check_complete(self):
346 346 isp = self.isp
347 347 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
348 348 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
349 349 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
350 350 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
351 self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
351 352
352 353 class InteractiveLoopTestCase(unittest.TestCase):
353 354 """Tests for an interactive loop like a python shell.
354 355 """
355 356 def check_ns(self, lines, ns):
356 357 """Validate that the given input lines produce the resulting namespace.
357 358
358 359 Note: the input lines are given exactly as they would be typed in an
359 360 auto-indenting environment, as mini_interactive_loop above already does
360 361 auto-indenting and prepends spaces to the input.
361 362 """
362 363 src = mini_interactive_loop(pseudo_input(lines))
363 364 test_ns = {}
364 365 exec(src, test_ns)
365 366 # We can't check that the provided ns is identical to the test_ns,
366 367 # because Python fills test_ns with extra keys (copyright, etc). But
367 368 # we can check that the given dict is *contained* in test_ns
368 369 for k,v in ns.items():
369 370 self.assertEqual(test_ns[k], v)
370 371
371 372 def test_simple(self):
372 373 self.check_ns(['x=1'], dict(x=1))
373 374
374 375 def test_simple2(self):
375 376 self.check_ns(['if 1:', 'x=2'], dict(x=2))
376 377
377 378 def test_xy(self):
378 379 self.check_ns(['x=1; y=2'], dict(x=1, y=2))
379 380
380 381 def test_abc(self):
381 382 self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
382 383
383 384 def test_multi(self):
384 385 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
385 386
386 387
387 388 class IPythonInputTestCase(InputSplitterTestCase):
388 389 """By just creating a new class whose .isp is a different instance, we
389 390 re-run the same test battery on the new input splitter.
390 391
391 392 In addition, this runs the tests over the syntax and syntax_ml dicts that
392 393 were tested by individual functions, as part of the OO interface.
393 394
394 395 It also makes some checks on the raw buffer storage.
395 396 """
396 397
397 398 def setUp(self):
398 399 self.isp = isp.IPythonInputSplitter()
399 400
400 401 def test_syntax(self):
401 402 """Call all single-line syntax tests from the main object"""
402 403 isp = self.isp
403 404 for example in syntax.values():
404 405 for raw, out_t in example:
405 406 if raw.startswith(' '):
406 407 continue
407 408
408 409 isp.push(raw+'\n')
409 410 out_raw = isp.source_raw
410 411 out = isp.source_reset()
411 412 self.assertEqual(out.rstrip(), out_t,
412 413 tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
413 414 self.assertEqual(out_raw.rstrip(), raw.rstrip())
414 415
415 416 def test_syntax_multiline(self):
416 417 isp = self.isp
417 418 for example in syntax_ml.values():
418 419 for line_pairs in example:
419 420 out_t_parts = []
420 421 raw_parts = []
421 422 for lraw, out_t_part in line_pairs:
422 423 if out_t_part is not None:
423 424 out_t_parts.append(out_t_part)
424 425
425 426 if lraw is not None:
426 427 isp.push(lraw)
427 428 raw_parts.append(lraw)
428 429
429 430 out_raw = isp.source_raw
430 431 out = isp.source_reset()
431 432 out_t = '\n'.join(out_t_parts).rstrip()
432 433 raw = '\n'.join(raw_parts).rstrip()
433 434 self.assertEqual(out.rstrip(), out_t)
434 435 self.assertEqual(out_raw.rstrip(), raw)
435 436
436 437 def test_syntax_multiline_cell(self):
437 438 isp = self.isp
438 439 for example in syntax_ml.values():
439 440
440 441 out_t_parts = []
441 442 for line_pairs in example:
442 443 raw = '\n'.join(r for r, _ in line_pairs if r is not None)
443 444 out_t = '\n'.join(t for _,t in line_pairs if t is not None)
444 445 out = isp.transform_cell(raw)
445 446 # Match ignoring trailing whitespace
446 447 self.assertEqual(out.rstrip(), out_t.rstrip())
447 448
448 449 def test_cellmagic_preempt(self):
449 450 isp = self.isp
450 451 for raw, name, line, cell in [
451 452 ("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
452 453 ("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
453 454 (">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
454 455 ("%%cellm \n>>> hi", u'cellm', u'', u'hi'),
455 456 ("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
456 457 ("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
457 458 ]:
458 459 expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
459 460 name, line, cell
460 461 )
461 462 out = isp.transform_cell(raw)
462 463 self.assertEqual(out.rstrip(), expected.rstrip())
463 464
464 465 def test_multiline_passthrough(self):
465 466 isp = self.isp
466 467 class CommentTransformer(InputTransformer):
467 468 def __init__(self):
468 469 self._lines = []
469 470
470 471 def push(self, line):
471 472 self._lines.append(line + '#')
472 473
473 474 def reset(self):
474 475 text = '\n'.join(self._lines)
475 476 self._lines = []
476 477 return text
477 478
478 479 isp.physical_line_transforms.insert(0, CommentTransformer())
479 480
480 481 for raw, expected in [
481 482 ("a=5", "a=5#"),
482 483 ("%ls foo", "get_ipython().magic(%r)" % u'ls foo#'),
483 484 ("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().magic(%r)" % (
484 485 u'ls foo#', u'ls bar#'
485 486 )),
486 487 ("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().magic(%r)\n4#\n5#" % u'ls foo#'),
487 488 ]:
488 489 out = isp.transform_cell(raw)
489 490 self.assertEqual(out.rstrip(), expected.rstrip())
490 491
491 492 #-----------------------------------------------------------------------------
492 493 # Main - use as a script, mostly for developer experiments
493 494 #-----------------------------------------------------------------------------
494 495
495 496 if __name__ == '__main__':
496 497 # A simple demo for interactive experimentation. This code will not get
497 498 # picked up by any test suite.
498 499 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
499 500
500 501 # configure here the syntax to use, prompt and whether to autoindent
501 502 #isp, start_prompt = InputSplitter(), '>>> '
502 503 isp, start_prompt = IPythonInputSplitter(), 'In> '
503 504
504 505 autoindent = True
505 506 #autoindent = False
506 507
507 508 try:
508 509 while True:
509 510 prompt = start_prompt
510 511 while isp.push_accepts_more():
511 512 indent = ' '*isp.indent_spaces
512 513 if autoindent:
513 514 line = indent + input(prompt+indent)
514 515 else:
515 516 line = input(prompt)
516 517 isp.push(line)
517 518 prompt = '... '
518 519
519 520 # Here we just return input so we can use it in a test suite, but a
520 521 # real interpreter would instead send it for execution somewhere.
521 522 #src = isp.source; raise EOFError # dbg
522 523 raw = isp.source_raw
523 524 src = isp.source_reset()
524 525 print('Input source was:\n', src)
525 526 print('Raw source was:\n', raw)
526 527 except EOFError:
527 528 print('Bye')
528 529
529 530 # Tests for cell magics support
530 531
531 532 def test_last_blank():
532 533 nt.assert_false(isp.last_blank(''))
533 534 nt.assert_false(isp.last_blank('abc'))
534 535 nt.assert_false(isp.last_blank('abc\n'))
535 536 nt.assert_false(isp.last_blank('abc\na'))
536 537
537 538 nt.assert_true(isp.last_blank('\n'))
538 539 nt.assert_true(isp.last_blank('\n '))
539 540 nt.assert_true(isp.last_blank('abc\n '))
540 541 nt.assert_true(isp.last_blank('abc\n\n'))
541 542 nt.assert_true(isp.last_blank('abc\nd\n\n'))
542 543 nt.assert_true(isp.last_blank('abc\nd\ne\n\n'))
543 544 nt.assert_true(isp.last_blank('abc \n \n \n\n'))
544 545
545 546
546 547 def test_last_two_blanks():
547 548 nt.assert_false(isp.last_two_blanks(''))
548 549 nt.assert_false(isp.last_two_blanks('abc'))
549 550 nt.assert_false(isp.last_two_blanks('abc\n'))
550 551 nt.assert_false(isp.last_two_blanks('abc\n\na'))
551 552 nt.assert_false(isp.last_two_blanks('abc\n \n'))
552 553 nt.assert_false(isp.last_two_blanks('abc\n\n'))
553 554
554 555 nt.assert_true(isp.last_two_blanks('\n\n'))
555 556 nt.assert_true(isp.last_two_blanks('\n\n '))
556 557 nt.assert_true(isp.last_two_blanks('\n \n'))
557 558 nt.assert_true(isp.last_two_blanks('abc\n\n '))
558 559 nt.assert_true(isp.last_two_blanks('abc\n\n\n'))
559 560 nt.assert_true(isp.last_two_blanks('abc\n\n \n'))
560 561 nt.assert_true(isp.last_two_blanks('abc\n\n \n '))
561 562 nt.assert_true(isp.last_two_blanks('abc\n\n \n \n'))
562 563 nt.assert_true(isp.last_two_blanks('abc\nd\n\n\n'))
563 564 nt.assert_true(isp.last_two_blanks('abc\nd\ne\nf\n\n\n'))
564 565
565 566
566 567 class CellMagicsCommon(object):
567 568
568 569 def test_whole_cell(self):
569 570 src = "%%cellm line\nbody\n"
570 571 out = self.sp.transform_cell(src)
571 572 ref = u"get_ipython().run_cell_magic({u}'cellm', {u}'line', {u}'body')\n"
572 573 nt.assert_equal(out, py3compat.u_format(ref))
573 574
574 575 def test_cellmagic_help(self):
575 576 self.sp.push('%%cellm?')
576 577 nt.assert_false(self.sp.push_accepts_more())
577 578
578 579 def tearDown(self):
579 580 self.sp.reset()
580 581
581 582
582 583 class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
583 584 sp = isp.IPythonInputSplitter(line_input_checker=False)
584 585
585 586 def test_incremental(self):
586 587 sp = self.sp
587 588 sp.push('%%cellm firstline\n')
588 589 nt.assert_true(sp.push_accepts_more()) #1
589 590 sp.push('line2\n')
590 591 nt.assert_true(sp.push_accepts_more()) #2
591 592 sp.push('\n')
592 593 # This should accept a blank line and carry on until the cell is reset
593 594 nt.assert_true(sp.push_accepts_more()) #3
594 595
595 596 class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
596 597 sp = isp.IPythonInputSplitter(line_input_checker=True)
597 598
598 599 def test_incremental(self):
599 600 sp = self.sp
600 601 sp.push('%%cellm line2\n')
601 602 nt.assert_true(sp.push_accepts_more()) #1
602 603 sp.push('\n')
603 604 # In this case, a blank line should end the cell magic
604 605 nt.assert_false(sp.push_accepts_more()) #2
General Comments 0
You need to be logged in to leave comments. Login now