##// END OF EJS Templates
Simplify input transformers...
Thomas Kluyver -
Show More
@@ -1,712 +1,712 b''
1 1 """Analysis of text input into executable blocks.
2 2
3 3 The main class in this module, :class:`InputSplitter`, is designed to break
4 4 input from either interactive, line-by-line environments or block-based ones,
5 5 into standalone blocks that can be executed by Python as 'single' statements
6 6 (thus triggering sys.displayhook).
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
11 11 For more details, see the class docstring below.
12 12
13 13 Syntax Transformations
14 14 ----------------------
15 15
16 16 One of the main jobs of the code in this file is to apply all syntax
17 17 transformations that make up 'the IPython language', i.e. magics, shell
18 18 escapes, etc. All transformations should be implemented as *fully stateless*
19 19 entities, that simply take one line as their input and return a line.
20 20 Internally for implementation purposes they may be a normal function or a
21 21 callable object, but the only input they receive will be a single line and they
22 22 should only return a line, without holding any data-dependent state between
23 23 calls.
24 24
25 25 As an example, the EscapedTransformer is a class so we can more clearly group
26 26 together the functionality of dispatching to individual functions based on the
27 27 starting escape character, but the only method for public use is its call
28 28 method.
29 29
30 30
31 31 ToDo
32 32 ----
33 33
34 34 - Should we make push() actually raise an exception once push_accepts_more()
35 35 returns False?
36 36
37 37 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
38 38 at least just attributes of a class so not really very exposed.
39 39
40 40 - Think about the best way to support dynamic things: automagic, autocall,
41 41 macros, etc.
42 42
43 43 - Think of a better heuristic for the application of the transforms in
44 44 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
45 45 track indentation change events (indent, dedent, nothing) and apply them only
46 46 if the indentation went up, but not otherwise.
47 47
48 48 - Think of the cleanest way for supporting user-specified transformations (the
49 49 user prefilters we had before).
50 50
51 51 Authors
52 52 -------
53 53
54 54 * Fernando Perez
55 55 * Brian Granger
56 56 """
57 57 #-----------------------------------------------------------------------------
58 58 # Copyright (C) 2010 The IPython Development Team
59 59 #
60 60 # Distributed under the terms of the BSD License. The full license is in
61 61 # the file COPYING, distributed as part of this software.
62 62 #-----------------------------------------------------------------------------
63 63
64 64 #-----------------------------------------------------------------------------
65 65 # Imports
66 66 #-----------------------------------------------------------------------------
67 67 # stdlib
68 68 import ast
69 69 import codeop
70 70 import re
71 71 import sys
72 72
73 73 # IPython modules
74 74 from IPython.core.splitinput import split_user_input, LineInfo
75 75 from IPython.utils.py3compat import cast_unicode
76 76 from IPython.core.inputtransformer import (leading_indent,
77 77 classic_prompt,
78 78 ipy_prompt,
79 79 cellmagic,
80 80 assemble_logical_lines,
81 81 help_end,
82 escaped_transformer,
82 escaped_commands,
83 83 assign_from_magic,
84 84 assign_from_system,
85 85 assemble_python_lines,
86 86 )
87 87
88 88 # Temporary!
89 89 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
90 90 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
91 91 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
92 92
93 93 #-----------------------------------------------------------------------------
94 94 # Utilities
95 95 #-----------------------------------------------------------------------------
96 96
97 97 # FIXME: These are general-purpose utilities that later can be moved to the
98 98 # general ward. Kept here for now because we're being very strict about test
99 99 # coverage with this code, and this lets us ensure that we keep 100% coverage
100 100 # while developing.
101 101
102 102 # compiled regexps for autoindent management
103 103 dedent_re = re.compile('|'.join([
104 104 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
105 105 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
106 106 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
107 107 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
108 108 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
109 109 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
110 110 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
111 111 ]))
112 112 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
113 113
114 114 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
115 115 # before pure comments
116 116 comment_line_re = re.compile('^\s*\#')
117 117
118 118
119 119 def num_ini_spaces(s):
120 120 """Return the number of initial spaces in a string.
121 121
122 122 Note that tabs are counted as a single space. For now, we do *not* support
123 123 mixing of tabs and spaces in the user's input.
124 124
125 125 Parameters
126 126 ----------
127 127 s : string
128 128
129 129 Returns
130 130 -------
131 131 n : int
132 132 """
133 133
134 134 ini_spaces = ini_spaces_re.match(s)
135 135 if ini_spaces:
136 136 return ini_spaces.end()
137 137 else:
138 138 return 0
139 139
140 140 def last_blank(src):
141 141 """Determine if the input source ends in a blank.
142 142
143 143 A blank is either a newline or a line consisting of whitespace.
144 144
145 145 Parameters
146 146 ----------
147 147 src : string
148 148 A single or multiline string.
149 149 """
150 150 if not src: return False
151 151 ll = src.splitlines()[-1]
152 152 return (ll == '') or ll.isspace()
153 153
154 154
155 155 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
156 156 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
157 157
158 158 def last_two_blanks(src):
159 159 """Determine if the input source ends in two blanks.
160 160
161 161 A blank is either a newline or a line consisting of whitespace.
162 162
163 163 Parameters
164 164 ----------
165 165 src : string
166 166 A single or multiline string.
167 167 """
168 168 if not src: return False
169 169 # The logic here is tricky: I couldn't get a regexp to work and pass all
170 170 # the tests, so I took a different approach: split the source by lines,
171 171 # grab the last two and prepend '###\n' as a stand-in for whatever was in
172 172 # the body before the last two lines. Then, with that structure, it's
173 173 # possible to analyze with two regexps. Not the most elegant solution, but
174 174 # it works. If anyone tries to change this logic, make sure to validate
175 175 # the whole test suite first!
176 176 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
177 177 return (bool(last_two_blanks_re.match(new_src)) or
178 178 bool(last_two_blanks_re2.match(new_src)) )
179 179
180 180
181 181 def remove_comments(src):
182 182 """Remove all comments from input source.
183 183
184 184 Note: comments are NOT recognized inside of strings!
185 185
186 186 Parameters
187 187 ----------
188 188 src : string
189 189 A single or multiline input string.
190 190
191 191 Returns
192 192 -------
193 193 String with all Python comments removed.
194 194 """
195 195
196 196 return re.sub('#.*', '', src)
197 197
198 198
199 199 def get_input_encoding():
200 200 """Return the default standard input encoding.
201 201
202 202 If sys.stdin has no encoding, 'ascii' is returned."""
203 203 # There are strange environments for which sys.stdin.encoding is None. We
204 204 # ensure that a valid encoding is returned.
205 205 encoding = getattr(sys.stdin, 'encoding', None)
206 206 if encoding is None:
207 207 encoding = 'ascii'
208 208 return encoding
209 209
210 210 #-----------------------------------------------------------------------------
211 211 # Classes and functions for normal Python syntax handling
212 212 #-----------------------------------------------------------------------------
213 213
214 214 class InputSplitter(object):
215 215 """An object that can accumulate lines of Python source before execution.
216 216
217 217 This object is designed to be fed python source line-by-line, using
218 218 :meth:`push`. It will return on each push whether the currently pushed
219 219 code could be executed already. In addition, it provides a method called
220 220 :meth:`push_accepts_more` that can be used to query whether more input
221 221 can be pushed into a single interactive block.
222 222
223 223 This is a simple example of how an interactive terminal-based client can use
224 224 this tool::
225 225
226 226 isp = InputSplitter()
227 227 while isp.push_accepts_more():
228 228 indent = ' '*isp.indent_spaces
229 229 prompt = '>>> ' + indent
230 230 line = indent + raw_input(prompt)
231 231 isp.push(line)
232 232 print 'Input source was:\n', isp.source_reset(),
233 233 """
234 234 # Number of spaces of indentation computed from input that has been pushed
235 235 # so far. This is the attributes callers should query to get the current
236 236 # indentation level, in order to provide auto-indent facilities.
237 237 indent_spaces = 0
238 238 # String, indicating the default input encoding. It is computed by default
239 239 # at initialization time via get_input_encoding(), but it can be reset by a
240 240 # client with specific knowledge of the encoding.
241 241 encoding = ''
242 242 # String where the current full source input is stored, properly encoded.
243 243 # Reading this attribute is the normal way of querying the currently pushed
244 244 # source code, that has been properly encoded.
245 245 source = ''
246 246 # Code object corresponding to the current source. It is automatically
247 247 # synced to the source, so it can be queried at any time to obtain the code
248 248 # object; it will be None if the source doesn't compile to valid Python.
249 249 code = None
250 250 # Input mode
251 251 input_mode = 'line'
252 252
253 253 # Private attributes
254 254
255 255 # List with lines of input accumulated so far
256 256 _buffer = None
257 257 # Command compiler
258 258 _compile = None
259 259 # Mark when input has changed indentation all the way back to flush-left
260 260 _full_dedent = False
261 261 # Boolean indicating whether the current block is complete
262 262 _is_complete = None
263 263
264 264 def __init__(self, input_mode=None):
265 265 """Create a new InputSplitter instance.
266 266
267 267 Parameters
268 268 ----------
269 269 input_mode : str
270 270
271 271 One of ['line', 'cell']; default is 'line'.
272 272
273 273 The input_mode parameter controls how new inputs are used when fed via
274 274 the :meth:`push` method:
275 275
276 276 - 'line': meant for line-oriented clients, inputs are appended one at a
277 277 time to the internal buffer and the whole buffer is compiled.
278 278
279 279 - 'cell': meant for clients that can edit multi-line 'cells' of text at
280 280 a time. A cell can contain one or more blocks that can be compile in
281 281 'single' mode by Python. In this mode, each new input new input
282 282 completely replaces all prior inputs. Cell mode is thus equivalent
283 283 to prepending a full reset() to every push() call.
284 284 """
285 285 self._buffer = []
286 286 self._compile = codeop.CommandCompiler()
287 287 self.encoding = get_input_encoding()
288 288 self.input_mode = InputSplitter.input_mode if input_mode is None \
289 289 else input_mode
290 290
291 291 def reset(self):
292 292 """Reset the input buffer and associated state."""
293 293 self.indent_spaces = 0
294 294 self._buffer[:] = []
295 295 self.source = ''
296 296 self.code = None
297 297 self._is_complete = False
298 298 self._full_dedent = False
299 299
300 300 def source_reset(self):
301 301 """Return the input source and perform a full reset.
302 302 """
303 303 out = self.source
304 304 self.reset()
305 305 return out
306 306
307 307 def push(self, lines):
308 308 """Push one or more lines of input.
309 309
310 310 This stores the given lines and returns a status code indicating
311 311 whether the code forms a complete Python block or not.
312 312
313 313 Any exceptions generated in compilation are swallowed, but if an
314 314 exception was produced, the method returns True.
315 315
316 316 Parameters
317 317 ----------
318 318 lines : string
319 319 One or more lines of Python input.
320 320
321 321 Returns
322 322 -------
323 323 is_complete : boolean
324 324 True if the current input source (the result of the current input
325 325 plus prior inputs) forms a complete Python execution block. Note that
326 326 this value is also stored as a private attribute (``_is_complete``), so it
327 327 can be queried at any time.
328 328 """
329 329 if self.input_mode == 'cell':
330 330 self.reset()
331 331
332 332 self._store(lines)
333 333 source = self.source
334 334
335 335 # Before calling _compile(), reset the code object to None so that if an
336 336 # exception is raised in compilation, we don't mislead by having
337 337 # inconsistent code/source attributes.
338 338 self.code, self._is_complete = None, None
339 339
340 340 # Honor termination lines properly
341 341 if source.endswith('\\\n'):
342 342 return False
343 343
344 344 self._update_indent(lines)
345 345 try:
346 346 self.code = self._compile(source, symbol="exec")
347 347 # Invalid syntax can produce any of a number of different errors from
348 348 # inside the compiler, so we have to catch them all. Syntax errors
349 349 # immediately produce a 'ready' block, so the invalid Python can be
350 350 # sent to the kernel for evaluation with possible ipython
351 351 # special-syntax conversion.
352 352 except (SyntaxError, OverflowError, ValueError, TypeError,
353 353 MemoryError):
354 354 self._is_complete = True
355 355 else:
356 356 # Compilation didn't produce any exceptions (though it may not have
357 357 # given a complete code object)
358 358 self._is_complete = self.code is not None
359 359
360 360 return self._is_complete
361 361
362 362 def push_accepts_more(self):
363 363 """Return whether a block of interactive input can accept more input.
364 364
365 365 This method is meant to be used by line-oriented frontends, who need to
366 366 guess whether a block is complete or not based solely on prior and
367 367 current input lines. The InputSplitter considers it has a complete
368 368 interactive block and will not accept more input only when either a
369 369 SyntaxError is raised, or *all* of the following are true:
370 370
371 371 1. The input compiles to a complete statement.
372 372
373 373 2. The indentation level is flush-left (because if we are indented,
374 374 like inside a function definition or for loop, we need to keep
375 375 reading new input).
376 376
377 377 3. There is one extra line consisting only of whitespace.
378 378
379 379 Because of condition #3, this method should be used only by
380 380 *line-oriented* frontends, since it means that intermediate blank lines
381 381 are not allowed in function definitions (or any other indented block).
382 382
383 383 If the current input produces a syntax error, this method immediately
384 384 returns False but does *not* raise the syntax error exception, as
385 385 typically clients will want to send invalid syntax to an execution
386 386 backend which might convert the invalid syntax into valid Python via
387 387 one of the dynamic IPython mechanisms.
388 388 """
389 389
390 390 # With incomplete input, unconditionally accept more
391 391 if not self._is_complete:
392 392 return True
393 393
394 394 # If we already have complete input and we're flush left, the answer
395 395 # depends. In line mode, if there hasn't been any indentation,
396 396 # that's it. If we've come back from some indentation, we need
397 397 # the blank final line to finish.
398 398 # In cell mode, we need to check how many blocks the input so far
399 399 # compiles into, because if there's already more than one full
400 400 # independent block of input, then the client has entered full
401 401 # 'cell' mode and is feeding lines that each is complete. In this
402 402 # case we should then keep accepting. The Qt terminal-like console
403 403 # does precisely this, to provide the convenience of terminal-like
404 404 # input of single expressions, but allowing the user (with a
405 405 # separate keystroke) to switch to 'cell' mode and type multiple
406 406 # expressions in one shot.
407 407 if self.indent_spaces==0:
408 408 if self.input_mode=='line':
409 409 if not self._full_dedent:
410 410 return False
411 411 else:
412 412 try:
413 413 code_ast = ast.parse(u''.join(self._buffer))
414 414 except Exception:
415 415 return False
416 416 else:
417 417 if len(code_ast.body) == 1:
418 418 return False
419 419
420 420 # When input is complete, then termination is marked by an extra blank
421 421 # line at the end.
422 422 last_line = self.source.splitlines()[-1]
423 423 return bool(last_line and not last_line.isspace())
424 424
425 425 #------------------------------------------------------------------------
426 426 # Private interface
427 427 #------------------------------------------------------------------------
428 428
429 429 def _find_indent(self, line):
430 430 """Compute the new indentation level for a single line.
431 431
432 432 Parameters
433 433 ----------
434 434 line : str
435 435 A single new line of non-whitespace, non-comment Python input.
436 436
437 437 Returns
438 438 -------
439 439 indent_spaces : int
440 440 New value for the indent level (it may be equal to self.indent_spaces
441 441 if indentation doesn't change.
442 442
443 443 full_dedent : boolean
444 444 Whether the new line causes a full flush-left dedent.
445 445 """
446 446 indent_spaces = self.indent_spaces
447 447 full_dedent = self._full_dedent
448 448
449 449 inisp = num_ini_spaces(line)
450 450 if inisp < indent_spaces:
451 451 indent_spaces = inisp
452 452 if indent_spaces <= 0:
453 453 #print 'Full dedent in text',self.source # dbg
454 454 full_dedent = True
455 455
456 456 if line.rstrip()[-1] == ':':
457 457 indent_spaces += 4
458 458 elif dedent_re.match(line):
459 459 indent_spaces -= 4
460 460 if indent_spaces <= 0:
461 461 full_dedent = True
462 462
463 463 # Safety
464 464 if indent_spaces < 0:
465 465 indent_spaces = 0
466 466 #print 'safety' # dbg
467 467
468 468 return indent_spaces, full_dedent
469 469
470 470 def _update_indent(self, lines):
471 471 for line in remove_comments(lines).splitlines():
472 472 if line and not line.isspace():
473 473 self.indent_spaces, self._full_dedent = self._find_indent(line)
474 474
475 475 def _store(self, lines, buffer=None, store='source'):
476 476 """Store one or more lines of input.
477 477
478 478 If input lines are not newline-terminated, a newline is automatically
479 479 appended."""
480 480
481 481 if buffer is None:
482 482 buffer = self._buffer
483 483
484 484 if lines.endswith('\n'):
485 485 buffer.append(lines)
486 486 else:
487 487 buffer.append(lines+'\n')
488 488 setattr(self, store, self._set_source(buffer))
489 489
490 490 def _set_source(self, buffer):
491 491 return u''.join(buffer)
492 492
493 493
494 494 class IPythonInputSplitter(InputSplitter):
495 495 """An input splitter that recognizes all of IPython's special syntax."""
496 496
497 497 # String with raw, untransformed input.
498 498 source_raw = ''
499 499
500 500 # Flag to track when a transformer has stored input that it hasn't given
501 501 # back yet.
502 502 transformer_accumulating = False
503 503
504 504 # Flag to track when assemble_python_lines has stored input that it hasn't
505 505 # given back yet.
506 506 within_python_line = False
507 507
508 508 # Private attributes
509 509
510 510 # List with lines of raw input accumulated so far.
511 511 _buffer_raw = None
512 512
513 513 def __init__(self, input_mode=None, physical_line_transforms=None,
514 514 logical_line_transforms=None, python_line_transforms=None):
515 515 super(IPythonInputSplitter, self).__init__(input_mode)
516 516 self._buffer_raw = []
517 517 self._validate = True
518 518
519 519 self.physical_line_transforms = physical_line_transforms or \
520 520 [leading_indent(),
521 521 classic_prompt(),
522 522 ipy_prompt(),
523 523 ]
524 524
525 525 self.assemble_logical_lines = assemble_logical_lines()
526 526 self.logical_line_transforms = logical_line_transforms or \
527 527 [cellmagic(),
528 528 help_end(),
529 escaped_transformer(),
529 escaped_commands(),
530 530 assign_from_magic(),
531 531 assign_from_system(),
532 532 ]
533 533
534 534 self.assemble_python_lines = assemble_python_lines()
535 535 self.python_line_transforms = python_line_transforms or []
536 536
537 537 @property
538 538 def transforms(self):
539 539 "Quick access to all transformers."
540 540 return self.physical_line_transforms + \
541 541 [self.assemble_logical_lines] + self.logical_line_transforms + \
542 542 [self.assemble_python_lines] + self.python_line_transforms
543 543
544 544 @property
545 545 def transforms_in_use(self):
546 546 """Transformers, excluding logical line transformers if we're in a
547 547 Python line."""
548 548 t = self.physical_line_transforms + [self.assemble_logical_lines]
549 549 if not self.within_python_line:
550 550 t += self.logical_line_transforms
551 551 return t + [self.assemble_python_lines] + self.python_line_transforms
552 552
553 553 def reset(self):
554 554 """Reset the input buffer and associated state."""
555 555 super(IPythonInputSplitter, self).reset()
556 556 self._buffer_raw[:] = []
557 557 self.source_raw = ''
558 558 self.transformer_accumulating = False
559 559 for t in self.transforms:
560 560 t.reset()
561 561
562 562 def flush_transformers(self):
563 563 def _flush(transform, out):
564 564 if out is not None:
565 565 tmp = transform.push(out)
566 566 return tmp or transform.reset() or None
567 567 else:
568 568 return transform.reset() or None
569 569
570 570 out = None
571 571 for t in self.transforms_in_use:
572 572 out = _flush(t, out)
573 573
574 574 if out is not None:
575 575 self._store(out)
576 576
577 577 def source_raw_reset(self):
578 578 """Return input and raw source and perform a full reset.
579 579 """
580 580 self.flush_transformers()
581 581 out = self.source
582 582 out_r = self.source_raw
583 583 self.reset()
584 584 return out, out_r
585 585
586 586 def source_reset(self):
587 587 self.flush_transformers()
588 588 return super(IPythonInputSplitter, self).source_reset()
589 589
590 590 def push_accepts_more(self):
591 591 if self.transformer_accumulating:
592 592 return True
593 593 else:
594 594 return super(IPythonInputSplitter, self).push_accepts_more()
595 595
596 596 def transform_cell(self, cell):
597 597 """Process and translate a cell of input.
598 598 """
599 599 self.reset()
600 600 self.push(cell)
601 601 return self.source_reset()
602 602
603 603 def push(self, lines):
604 604 """Push one or more lines of IPython input.
605 605
606 606 This stores the given lines and returns a status code indicating
607 607 whether the code forms a complete Python block or not, after processing
608 608 all input lines for special IPython syntax.
609 609
610 610 Any exceptions generated in compilation are swallowed, but if an
611 611 exception was produced, the method returns True.
612 612
613 613 Parameters
614 614 ----------
615 615 lines : string
616 616 One or more lines of Python input.
617 617
618 618 Returns
619 619 -------
620 620 is_complete : boolean
621 621 True if the current input source (the result of the current input
622 622 plus prior inputs) forms a complete Python execution block. Note that
623 623 this value is also stored as a private attribute (_is_complete), so it
624 624 can be queried at any time.
625 625 """
626 626
627 627 # We must ensure all input is pure unicode
628 628 lines = cast_unicode(lines, self.encoding)
629 629
630 630 # ''.splitlines() --> [], but we need to push the empty line to transformers
631 631 lines_list = lines.splitlines()
632 632 if not lines_list:
633 633 lines_list = ['']
634 634
635 635 # Transform logic
636 636 #
637 637 # We only apply the line transformers to the input if we have either no
638 638 # input yet, or complete input, or if the last line of the buffer ends
639 639 # with ':' (opening an indented block). This prevents the accidental
640 640 # transformation of escapes inside multiline expressions like
641 641 # triple-quoted strings or parenthesized expressions.
642 642 #
643 643 # The last heuristic, while ugly, ensures that the first line of an
644 644 # indented block is correctly transformed.
645 645 #
646 646 # FIXME: try to find a cleaner approach for this last bit.
647 647
648 648 # If we were in 'block' mode, since we're going to pump the parent
649 649 # class by hand line by line, we need to temporarily switch out to
650 650 # 'line' mode, do a single manual reset and then feed the lines one
651 651 # by one. Note that this only matters if the input has more than one
652 652 # line.
653 653 changed_input_mode = False
654 654
655 655 if self.input_mode == 'cell':
656 656 self.reset()
657 657 changed_input_mode = True
658 658 saved_input_mode = 'cell'
659 659 self.input_mode = 'line'
660 660
661 661 # Store raw source before applying any transformations to it. Note
662 662 # that this must be done *after* the reset() call that would otherwise
663 663 # flush the buffer.
664 664 self._store(lines, self._buffer_raw, 'source_raw')
665 665
666 666 try:
667 667 for line in lines_list:
668 668 out = self.push_line(line)
669 669 finally:
670 670 if changed_input_mode:
671 671 self.input_mode = saved_input_mode
672 672
673 673 return out
674 674
675 675 def push_line(self, line):
676 676 buf = self._buffer
677 677
678 678 def _accumulating(dbg):
679 679 #print(dbg)
680 680 self.transformer_accumulating = True
681 681 return False
682 682
683 683 for transformer in self.physical_line_transforms:
684 684 line = transformer.push(line)
685 685 if line is None:
686 686 return _accumulating(transformer)
687 687
688 688 line = self.assemble_logical_lines.push(line)
689 689 if line is None:
690 690 return _accumulating('acc logical line')
691 691
692 692 if not self.within_python_line:
693 693 for transformer in self.logical_line_transforms:
694 694 line = transformer.push(line)
695 695 if line is None:
696 696 return _accumulating(transformer)
697 697
698 698 line = self.assemble_python_lines.push(line)
699 699 if line is None:
700 700 self.within_python_line = True
701 701 return _accumulating('acc python line')
702 702 else:
703 703 self.within_python_line = False
704 704
705 705 for transformer in self.python_line_transforms:
706 706 line = transformer.push(line)
707 707 if line is None:
708 708 return _accumulating(transformer)
709 709
710 710 #print("transformers clear") #debug
711 711 self.transformer_accumulating = False
712 712 return super(IPythonInputSplitter, self).push(line)
@@ -1,487 +1,440 b''
1 1 import abc
2 2 import functools
3 3 import re
4 4 from StringIO import StringIO
5 5 import tokenize
6 6
7 7 try:
8 8 generate_tokens = tokenize.generate_tokens
9 9 except AttributeError:
10 10 # Python 3. Note that we use the undocumented _tokenize because it expects
11 11 # strings, not bytes. See also Python issue #9969.
12 12 generate_tokens = tokenize._tokenize
13 13
14 14 from IPython.core.splitinput import split_user_input, LineInfo
15 15 from IPython.utils.untokenize import untokenize
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Globals
19 19 #-----------------------------------------------------------------------------
20 20
21 21 # The escape sequences that define the syntax transformations IPython will
22 22 # apply to user input. These can NOT be just changed here: many regular
23 23 # expressions and other parts of the code may use their hardcoded values, and
24 24 # for all intents and purposes they constitute the 'IPython syntax', so they
25 25 # should be considered fixed.
26 26
27 27 ESC_SHELL = '!' # Send line to underlying system shell
28 28 ESC_SH_CAP = '!!' # Send line to system shell and capture output
29 29 ESC_HELP = '?' # Find information about object
30 30 ESC_HELP2 = '??' # Find extra-detailed information about object
31 31 ESC_MAGIC = '%' # Call magic function
32 32 ESC_MAGIC2 = '%%' # Call cell-magic function
33 33 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
34 34 ESC_QUOTE2 = ';' # Quote all args as a single string, call
35 35 ESC_PAREN = '/' # Call first argument with rest of line as arguments
36 36
37 37 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
38 38 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
39 39 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
40 40
41 41
42 42 class InputTransformer(object):
43 43 """Abstract base class for line-based input transformers."""
44 44 __metaclass__ = abc.ABCMeta
45 45
46 46 @abc.abstractmethod
47 47 def push(self, line):
48 48 """Send a line of input to the transformer, returning the transformed
49 49 input or None if the transformer is waiting for more input.
50 50
51 51 Must be overridden by subclasses.
52 52 """
53 53 pass
54 54
55 55 @abc.abstractmethod
56 56 def reset(self):
57 57 """Return, transformed any lines that the transformer has accumulated,
58 58 and reset its internal state.
59 59
60 60 Must be overridden by subclasses.
61 61 """
62 62 pass
63 63
64 # Set this to True to allow the transformer to act on lines inside strings.
65 look_in_string = False
66
67 64 @classmethod
68 65 def wrap(cls, func):
69 66 """Can be used by subclasses as a decorator, to return a factory that
70 67 will allow instantiation with the decorated object.
71 68 """
72 69 @functools.wraps(func)
73 70 def transformer_factory():
74 transformer = cls(func)
75 if getattr(transformer_factory, 'look_in_string', False):
76 transformer.look_in_string = True
77 return transformer
71 return cls(func)
78 72
79 73 return transformer_factory
80 74
81 75 class StatelessInputTransformer(InputTransformer):
82 76 """Wrapper for a stateless input transformer implemented as a function."""
83 77 def __init__(self, func):
84 78 self.func = func
85 79
86 80 def __repr__(self):
87 81 return "StatelessInputTransformer(func={!r})".format(self.func)
88 82
89 83 def push(self, line):
90 84 """Send a line of input to the transformer, returning the
91 85 transformed input."""
92 86 return self.func(line)
93 87
94 88 def reset(self):
95 89 """No-op - exists for compatibility."""
96 90 pass
97 91
98 92 class CoroutineInputTransformer(InputTransformer):
99 93 """Wrapper for an input transformer implemented as a coroutine."""
100 94 def __init__(self, coro):
101 95 # Prime it
102 96 self.coro = coro()
103 97 next(self.coro)
104 98
105 99 def __repr__(self):
106 100 return "CoroutineInputTransformer(coro={!r})".format(self.coro)
107 101
108 102 def push(self, line):
109 103 """Send a line of input to the transformer, returning the
110 104 transformed input or None if the transformer is waiting for more
111 105 input.
112 106 """
113 107 return self.coro.send(line)
114 108
115 109 def reset(self):
116 110 """Return, transformed any lines that the transformer has
117 111 accumulated, and reset its internal state.
118 112 """
119 113 return self.coro.send(None)
120 114
121 115 class TokenInputTransformer(InputTransformer):
122 116 """Wrapper for a token-based input transformer.
123 117
124 118 func should accept a list of tokens (5-tuples, see tokenize docs), and
125 119 return an iterable which can be passed to tokenize.untokenize().
126 120 """
127 121 def __init__(self, func):
128 122 self.func = func
129 123 self.current_line = ""
130 124 self.line_used = False
131 125 self.reset_tokenizer()
132 126
133 127 def reset_tokenizer(self):
134 128 self.tokenizer = generate_tokens(self.get_line)
135 129
136 130 def get_line(self):
137 131 if self.line_used:
138 132 raise tokenize.TokenError
139 133 self.line_used = True
140 134 return self.current_line
141 135
142 136 def push(self, line):
143 137 self.current_line += line + "\n"
144 138 if self.current_line.isspace():
145 139 return self.reset()
146 140
147 141 self.line_used = False
148 142 tokens = []
149 143 stop_at_NL = False
150 144 try:
151 145 for intok in self.tokenizer:
152 146 tokens.append(intok)
153 147 t = intok[0]
154 148 if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
155 149 # Stop before we try to pull a line we don't have yet
156 150 break
157 151 elif t in (tokenize.COMMENT, tokenize.ERRORTOKEN):
158 152 stop_at_NL = True
159 153 except tokenize.TokenError:
160 154 # Multi-line statement - stop and try again with the next line
161 155 self.reset_tokenizer()
162 156 return None
163 157
164 158 return self.output(tokens)
165 159
166 160 def output(self, tokens):
167 161 self.current_line = ""
168 162 self.reset_tokenizer()
169 163 return untokenize(self.func(tokens)).rstrip('\n')
170 164
171 165 def reset(self):
172 166 l = self.current_line
173 167 self.current_line = ""
174 168 self.reset_tokenizer()
175 169 if l:
176 170 return l.rstrip('\n')
177 171
178 172 class assemble_python_lines(TokenInputTransformer):
179 173 def __init__(self):
180 174 super(assemble_python_lines, self).__init__(None)
181 175
182 176 def output(self, tokens):
183 177 return self.reset()
184 178
185 179 @CoroutineInputTransformer.wrap
186 180 def assemble_logical_lines():
187 181 """Join lines following explicit line continuations (\)"""
188 182 line = ''
189 183 while True:
190 184 line = (yield line)
191 185 if not line or line.isspace():
192 186 continue
193 187
194 188 parts = []
195 189 while line is not None:
196 190 parts.append(line.rstrip('\\'))
197 191 if not line.endswith('\\'):
198 192 break
199 193 line = (yield None)
200 194
201 195 # Output
202 196 line = ' '.join(parts)
203 197
204 198 # Utilities
205 199 def _make_help_call(target, esc, lspace, next_input=None):
206 200 """Prepares a pinfo(2)/psearch call from a target name and the escape
207 201 (i.e. ? or ??)"""
208 202 method = 'pinfo2' if esc == '??' \
209 203 else 'psearch' if '*' in target \
210 204 else 'pinfo'
211 205 arg = " ".join([method, target])
212 206 if next_input is None:
213 207 return '%sget_ipython().magic(%r)' % (lspace, arg)
214 208 else:
215 209 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
216 210 (lspace, next_input, arg)
217
218 @CoroutineInputTransformer.wrap
219 def escaped_transformer():
220 """Translate lines beginning with one of IPython's escape characters.
221 211
222 This is stateful to allow magic commands etc. to be continued over several
223 lines using explicit line continuations (\ at the end of a line).
212 # These define the transformations for the different escape characters.
213 def _tr_system(line_info):
214 "Translate lines escaped with: !"
215 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
216 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
217
218 def _tr_system2(line_info):
219 "Translate lines escaped with: !!"
220 cmd = line_info.line.lstrip()[2:]
221 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
222
223 def _tr_help(line_info):
224 "Translate lines escaped with: ?/??"
225 # A naked help line should just fire the intro help screen
226 if not line_info.line[1:]:
227 return 'get_ipython().show_usage()'
228
229 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
230
231 def _tr_magic(line_info):
232 "Translate lines escaped with: %"
233 tpl = '%sget_ipython().magic(%r)'
234 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
235 return tpl % (line_info.pre, cmd)
236
237 def _tr_quote(line_info):
238 "Translate lines escaped with: ,"
239 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
240 '", "'.join(line_info.the_rest.split()) )
241
242 def _tr_quote2(line_info):
243 "Translate lines escaped with: ;"
244 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
245 line_info.the_rest)
246
247 def _tr_paren(line_info):
248 "Translate lines escaped with: /"
249 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
250 ", ".join(line_info.the_rest.split()))
251
252 tr = { ESC_SHELL : _tr_system,
253 ESC_SH_CAP : _tr_system2,
254 ESC_HELP : _tr_help,
255 ESC_HELP2 : _tr_help,
256 ESC_MAGIC : _tr_magic,
257 ESC_QUOTE : _tr_quote,
258 ESC_QUOTE2 : _tr_quote2,
259 ESC_PAREN : _tr_paren }
260
261 @StatelessInputTransformer.wrap
262 def escaped_commands(line):
263 """Transform escaped commands - %magic, !system, ?help + various autocalls.
224 264 """
265 if not line or line.isspace():
266 return line
267 lineinf = LineInfo(line)
268 if lineinf.esc not in tr:
269 return line
225 270
226 # These define the transformations for the different escape characters.
227 def _tr_system(line_info):
228 "Translate lines escaped with: !"
229 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
230 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
231
232 def _tr_system2(line_info):
233 "Translate lines escaped with: !!"
234 cmd = line_info.line.lstrip()[2:]
235 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
236
237 def _tr_help(line_info):
238 "Translate lines escaped with: ?/??"
239 # A naked help line should just fire the intro help screen
240 if not line_info.line[1:]:
241 return 'get_ipython().show_usage()'
242
243 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
244
245 def _tr_magic(line_info):
246 "Translate lines escaped with: %"
247 tpl = '%sget_ipython().magic(%r)'
248 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
249 return tpl % (line_info.pre, cmd)
250
251 def _tr_quote(line_info):
252 "Translate lines escaped with: ,"
253 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
254 '", "'.join(line_info.the_rest.split()) )
255
256 def _tr_quote2(line_info):
257 "Translate lines escaped with: ;"
258 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
259 line_info.the_rest)
260
261 def _tr_paren(line_info):
262 "Translate lines escaped with: /"
263 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
264 ", ".join(line_info.the_rest.split()))
265
266 tr = { ESC_SHELL : _tr_system,
267 ESC_SH_CAP : _tr_system2,
268 ESC_HELP : _tr_help,
269 ESC_HELP2 : _tr_help,
270 ESC_MAGIC : _tr_magic,
271 ESC_QUOTE : _tr_quote,
272 ESC_QUOTE2 : _tr_quote2,
273 ESC_PAREN : _tr_paren }
274
275 line = ''
276 while True:
277 line = (yield line)
278 if not line or line.isspace():
279 continue
280 lineinf = LineInfo(line)
281 if lineinf.esc not in tr:
282 continue
283
284 parts = []
285 while line is not None:
286 parts.append(line.rstrip('\\'))
287 if not line.endswith('\\'):
288 break
289 line = (yield None)
290
291 # Output
292 lineinf = LineInfo(' '.join(parts))
293 line = tr[lineinf.esc](lineinf)
271 return tr[lineinf.esc](lineinf)
294 272
295 273 _initial_space_re = re.compile(r'\s*')
296 274
297 275 _help_end_re = re.compile(r"""(%{0,2}
298 276 [a-zA-Z_*][\w*]* # Variable name
299 277 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
300 278 )
301 279 (\?\??)$ # ? or ??""",
302 280 re.VERBOSE)
303 281
304 282 def has_comment(src):
305 283 """Indicate whether an input line has (i.e. ends in, or is) a comment.
306 284
307 285 This uses tokenize, so it can distinguish comments from # inside strings.
308 286
309 287 Parameters
310 288 ----------
311 289 src : string
312 290 A single line input string.
313 291
314 292 Returns
315 293 -------
316 294 Boolean: True if source has a comment.
317 295 """
318 296 readline = StringIO(src).readline
319 297 toktypes = set()
320 298 try:
321 299 for t in tokenize.generate_tokens(readline):
322 300 toktypes.add(t[0])
323 301 except tokenize.TokenError:
324 302 pass
325 303 return(tokenize.COMMENT in toktypes)
326 304
327 305
328 306 @StatelessInputTransformer.wrap
329 307 def help_end(line):
330 308 """Translate lines with ?/?? at the end"""
331 309 m = _help_end_re.search(line)
332 310 if m is None or has_comment(line):
333 311 return line
334 312 target = m.group(1)
335 313 esc = m.group(3)
336 314 lspace = _initial_space_re.match(line).group(0)
337 315
338 316 # If we're mid-command, put it back on the next prompt for the user.
339 317 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
340 318
341 319 return _make_help_call(target, esc, lspace, next_input)
342 320
343 321
344 322 @CoroutineInputTransformer.wrap
345 323 def cellmagic():
346 324 """Captures & transforms cell magics.
347 325
348 326 After a cell magic is started, this stores up any lines it gets until it is
349 327 reset (sent None).
350 328 """
351 329 tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
352 330 cellmagic_help_re = re.compile('%%\w+\?')
353 331 line = ''
354 332 while True:
355 333 line = (yield line)
356 334 if (not line) or (not line.startswith(ESC_MAGIC2)):
357 335 continue
358 336
359 337 if cellmagic_help_re.match(line):
360 338 # This case will be handled by help_end
361 339 continue
362 340
363 341 first = line
364 342 body = []
365 343 line = (yield None)
366 344 while (line is not None) and (line.strip() != ''):
367 345 body.append(line)
368 346 line = (yield None)
369 347
370 348 # Output
371 349 magic_name, _, first = first.partition(' ')
372 350 magic_name = magic_name.lstrip(ESC_MAGIC2)
373 351 line = tpl % (magic_name, first, u'\n'.join(body))
374 352
375 353
376 354 def _strip_prompts(prompt1_re, prompt2_re):
377 355 """Remove matching input prompts from a block of input."""
378 356 line = ''
379 357 while True:
380 358 line = (yield line)
381 359
382 360 if line is None:
383 361 continue
384 362
385 363 m = prompt1_re.match(line)
386 364 if m:
387 365 while m:
388 366 line = (yield line[len(m.group(0)):])
389 367 if line is None:
390 368 break
391 369 m = prompt2_re.match(line)
392 370 else:
393 371 # Prompts not in input - wait for reset
394 372 while line is not None:
395 373 line = (yield line)
396 374
397 375 @CoroutineInputTransformer.wrap
398 376 def classic_prompt():
399 377 """Strip the >>>/... prompts of the Python interactive shell."""
400 378 prompt1_re = re.compile(r'^(>>> )')
401 379 prompt2_re = re.compile(r'^(>>> |^\.\.\. )')
402 380 return _strip_prompts(prompt1_re, prompt2_re)
403 381
404 classic_prompt.look_in_string = True
405
406 382 @CoroutineInputTransformer.wrap
407 383 def ipy_prompt():
408 384 """Strip IPython's In [1]:/...: prompts."""
409 385 prompt1_re = re.compile(r'^In \[\d+\]: ')
410 386 prompt2_re = re.compile(r'^(In \[\d+\]: |^\ \ \ \.\.\.+: )')
411 387 return _strip_prompts(prompt1_re, prompt2_re)
412 388
413 ipy_prompt.look_in_string = True
414
415 389
416 390 @CoroutineInputTransformer.wrap
417 391 def leading_indent():
418 392 """Remove leading indentation.
419 393
420 394 If the first line starts with a spaces or tabs, the same whitespace will be
421 395 removed from each following line until it is reset.
422 396 """
423 397 space_re = re.compile(r'^[ \t]+')
424 398 line = ''
425 399 while True:
426 400 line = (yield line)
427 401
428 402 if line is None:
429 403 continue
430 404
431 405 m = space_re.match(line)
432 406 if m:
433 407 space = m.group(0)
434 408 while line is not None:
435 409 if line.startswith(space):
436 410 line = line[len(space):]
437 411 line = (yield line)
438 412 else:
439 413 # No leading spaces - wait for reset
440 414 while line is not None:
441 415 line = (yield line)
442 416
443 leading_indent.look_in_string = True
444
445 417
446 def _special_assignment(assignment_re, template):
447 """Transform assignment from system & magic commands.
448
449 This is stateful so that it can handle magic commands continued on several
450 lines.
451 """
452 line = ''
453 while True:
454 line = (yield line)
455 if not line or line.isspace():
456 continue
457
458 m = assignment_re.match(line)
459 if not m:
460 continue
461
462 parts = []
463 while line is not None:
464 parts.append(line.rstrip('\\'))
465 if not line.endswith('\\'):
466 break
467 line = (yield None)
468
469 # Output
470 whole = assignment_re.match(' '.join(parts))
471 line = template % (whole.group('lhs'), whole.group('cmd'))
472
473 @CoroutineInputTransformer.wrap
474 def assign_from_system():
418 assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
419 r'\s*=\s*!\s*(?P<cmd>.*)')
420 assign_system_template = '%s = get_ipython().getoutput(%r)'
421 @StatelessInputTransformer.wrap
422 def assign_from_system(line):
475 423 """Transform assignment from system commands (e.g. files = !ls)"""
476 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
477 r'\s*=\s*!\s*(?P<cmd>.*)')
478 template = '%s = get_ipython().getoutput(%r)'
479 return _special_assignment(assignment_re, template)
424 m = assign_system_re.match(line)
425 if m is None:
426 return line
427
428 return assign_system_template % m.group('lhs', 'cmd')
480 429
481 @CoroutineInputTransformer.wrap
482 def assign_from_magic():
430 assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
431 r'\s*=\s*%\s*(?P<cmd>.*)')
432 assign_magic_template = '%s = get_ipython().magic(%r)'
433 @StatelessInputTransformer.wrap
434 def assign_from_magic(line):
483 435 """Transform assignment from magic commands (e.g. a = %who_ls)"""
484 assignment_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
485 r'\s*=\s*%\s*(?P<cmd>.*)')
486 template = '%s = get_ipython().magic(%r)'
487 return _special_assignment(assignment_re, template)
436 m = assign_magic_re.match(line)
437 if m is None:
438 return line
439
440 return assign_magic_template % m.group('lhs', 'cmd')
@@ -1,368 +1,394 b''
1 1 import tokenize
2 2 import unittest
3 3 import nose.tools as nt
4 4
5 5 from IPython.testing import tools as tt
6 6 from IPython.utils import py3compat
7 7 u_fmt = py3compat.u_format
8 8
9 9 from IPython.core import inputtransformer as ipt
10 10
11 11 def transform_and_reset(transformer):
12 12 transformer = transformer()
13 13 def transform(inp):
14 14 try:
15 15 return transformer.push(inp)
16 16 finally:
17 17 transformer.reset()
18 18
19 19 return transform
20 20
21 21 # Transformer tests
22 22 def transform_checker(tests, transformer):
23 23 """Utility to loop over test inputs"""
24 24 transformer = transformer()
25 25 try:
26 26 for inp, tr in tests:
27 27 if inp is None:
28 28 out = transformer.reset()
29 29 else:
30 30 out = transformer.push(inp)
31 31 nt.assert_equal(out, tr)
32 32 finally:
33 33 transformer.reset()
34 34
35 35 # Data for all the syntax tests in the form of lists of pairs of
36 36 # raw/transformed input. We store it here as a global dict so that we can use
37 37 # it both within single-function tests and also to validate the behavior of the
38 38 # larger objects
39 39
40 40 syntax = \
41 41 dict(assign_system =
42 42 [(i,py3compat.u_format(o)) for i,o in \
43 43 [(u'a =! ls', "a = get_ipython().getoutput({u}'ls')"),
44 44 (u'b = !ls', "b = get_ipython().getoutput({u}'ls')"),
45 45 ('x=1', 'x=1'), # normal input is unmodified
46 46 (' ',' '), # blank lines are kept intact
47 47 ]],
48 48
49 49 assign_magic =
50 50 [(i,py3compat.u_format(o)) for i,o in \
51 51 [(u'a =% who', "a = get_ipython().magic({u}'who')"),
52 52 (u'b = %who', "b = get_ipython().magic({u}'who')"),
53 53 ('x=1', 'x=1'), # normal input is unmodified
54 54 (' ',' '), # blank lines are kept intact
55 55 ]],
56 56
57 57 classic_prompt =
58 58 [('>>> x=1', 'x=1'),
59 59 ('x=1', 'x=1'), # normal input is unmodified
60 60 (' ', ' '), # blank lines are kept intact
61 61 ],
62 62
63 63 ipy_prompt =
64 64 [('In [1]: x=1', 'x=1'),
65 65 ('x=1', 'x=1'), # normal input is unmodified
66 66 (' ',' '), # blank lines are kept intact
67 67 ],
68 68
69 69 # Tests for the escape transformer to leave normal code alone
70 70 escaped_noesc =
71 71 [ (' ', ' '),
72 72 ('x=1', 'x=1'),
73 73 ],
74 74
75 75 # System calls
76 76 escaped_shell =
77 77 [(i,py3compat.u_format(o)) for i,o in \
78 78 [ (u'!ls', "get_ipython().system({u}'ls')"),
79 79 # Double-escape shell, this means to capture the output of the
80 80 # subprocess and return it
81 81 (u'!!ls', "get_ipython().getoutput({u}'ls')"),
82 82 ]],
83 83
84 84 # Help/object info
85 85 escaped_help =
86 86 [(i,py3compat.u_format(o)) for i,o in \
87 87 [ (u'?', 'get_ipython().show_usage()'),
88 88 (u'?x1', "get_ipython().magic({u}'pinfo x1')"),
89 89 (u'??x2', "get_ipython().magic({u}'pinfo2 x2')"),
90 90 (u'?a.*s', "get_ipython().magic({u}'psearch a.*s')"),
91 91 (u'?%hist1', "get_ipython().magic({u}'pinfo %hist1')"),
92 92 (u'?%%hist2', "get_ipython().magic({u}'pinfo %%hist2')"),
93 93 (u'?abc = qwe', "get_ipython().magic({u}'pinfo abc')"),
94 94 ]],
95 95
96 96 end_help =
97 97 [(i,py3compat.u_format(o)) for i,o in \
98 98 [ (u'x3?', "get_ipython().magic({u}'pinfo x3')"),
99 99 (u'x4??', "get_ipython().magic({u}'pinfo2 x4')"),
100 100 (u'%hist1?', "get_ipython().magic({u}'pinfo %hist1')"),
101 101 (u'%hist2??', "get_ipython().magic({u}'pinfo2 %hist2')"),
102 102 (u'%%hist3?', "get_ipython().magic({u}'pinfo %%hist3')"),
103 103 (u'%%hist4??', "get_ipython().magic({u}'pinfo2 %%hist4')"),
104 104 (u'f*?', "get_ipython().magic({u}'psearch f*')"),
105 105 (u'ax.*aspe*?', "get_ipython().magic({u}'psearch ax.*aspe*')"),
106 106 (u'a = abc?', "get_ipython().set_next_input({u}'a = abc');"
107 107 "get_ipython().magic({u}'pinfo abc')"),
108 108 (u'a = abc.qe??', "get_ipython().set_next_input({u}'a = abc.qe');"
109 109 "get_ipython().magic({u}'pinfo2 abc.qe')"),
110 110 (u'a = *.items?', "get_ipython().set_next_input({u}'a = *.items');"
111 111 "get_ipython().magic({u}'psearch *.items')"),
112 112 (u'plot(a?', "get_ipython().set_next_input({u}'plot(a');"
113 113 "get_ipython().magic({u}'pinfo a')"),
114 114 (u'a*2 #comment?', 'a*2 #comment?'),
115 115 ]],
116 116
117 117 # Explicit magic calls
118 118 escaped_magic =
119 119 [(i,py3compat.u_format(o)) for i,o in \
120 120 [ (u'%cd', "get_ipython().magic({u}'cd')"),
121 121 (u'%cd /home', "get_ipython().magic({u}'cd /home')"),
122 122 # Backslashes need to be escaped.
123 123 (u'%cd C:\\User', "get_ipython().magic({u}'cd C:\\\\User')"),
124 124 (u' %magic', " get_ipython().magic({u}'magic')"),
125 125 ]],
126 126
127 127 # Quoting with separate arguments
128 128 escaped_quote =
129 129 [ (',f', 'f("")'),
130 130 (',f x', 'f("x")'),
131 131 (' ,f y', ' f("y")'),
132 132 (',f a b', 'f("a", "b")'),
133 133 ],
134 134
135 135 # Quoting with single argument
136 136 escaped_quote2 =
137 137 [ (';f', 'f("")'),
138 138 (';f x', 'f("x")'),
139 139 (' ;f y', ' f("y")'),
140 140 (';f a b', 'f("a b")'),
141 141 ],
142 142
143 143 # Simply apply parens
144 144 escaped_paren =
145 145 [ ('/f', 'f()'),
146 146 ('/f x', 'f(x)'),
147 147 (' /f y', ' f(y)'),
148 148 ('/f a b', 'f(a, b)'),
149 149 ],
150 150
151 151 # Check that we transform prompts before other transforms
152 152 mixed =
153 153 [(i,py3compat.u_format(o)) for i,o in \
154 154 [ (u'In [1]: %lsmagic', "get_ipython().magic({u}'lsmagic')"),
155 155 (u'>>> %lsmagic', "get_ipython().magic({u}'lsmagic')"),
156 156 (u'In [2]: !ls', "get_ipython().system({u}'ls')"),
157 157 (u'In [3]: abs?', "get_ipython().magic({u}'pinfo abs')"),
158 158 (u'In [4]: b = %who', "b = get_ipython().magic({u}'who')"),
159 159 ]],
160 160 )
161 161
162 162 # multiline syntax examples. Each of these should be a list of lists, with
163 163 # each entry itself having pairs of raw/transformed input. The union (with
164 164 # '\n'.join() of the transformed inputs is what the splitter should produce
165 165 # when fed the raw lines one at a time via push.
166 166 syntax_ml = \
167 167 dict(classic_prompt =
168 168 [ [('>>> for i in range(10):','for i in range(10):'),
169 169 ('... print i',' print i'),
170 170 ('... ', ''),
171 171 ],
172 172 [('>>> a="""','a="""'),
173 173 ('... 123"""','123"""'),
174 174 ],
175 175 [('a="""','a="""'),
176 176 ('... 123"""','... 123"""'),
177 177 ],
178 178 ],
179 179
180 180 ipy_prompt =
181 181 [ [('In [24]: for i in range(10):','for i in range(10):'),
182 182 (' ....: print i',' print i'),
183 183 (' ....: ', ''),
184 184 ],
185 185 [('In [2]: a="""','a="""'),
186 186 (' ...: 123"""','123"""'),
187 187 ],
188 188 [('a="""','a="""'),
189 189 (' ...: 123"""',' ...: 123"""'),
190 190 ],
191 191 ],
192 192
193 193 multiline_datastructure =
194 194 [ [('>>> a = [1,','a = [1,'),
195 195 ('... 2]','2]'),
196 196 ],
197 197 ],
198 198
199 199 leading_indent =
200 200 [ [(' print "hi"','print "hi"'),
201 201 ],
202 202 [(' for a in range(5):','for a in range(5):'),
203 203 (' a*2',' a*2'),
204 204 ],
205 205 [(' a="""','a="""'),
206 206 (' 123"""','123"""'),
207 207 ],
208 208 [('a="""','a="""'),
209 209 (' 123"""',' 123"""'),
210 210 ],
211 211 ],
212 212
213 213 cellmagic =
214 214 [ [(u'%%foo a', None),
215 215 (None, u_fmt("get_ipython().run_cell_magic({u}'foo', {u}'a', {u}'')")),
216 216 ],
217 217 [(u'%%bar 123', None),
218 218 (u'hello', None),
219 219 (u'', u_fmt("get_ipython().run_cell_magic({u}'bar', {u}'123', {u}'hello')")),
220 220 ],
221 221 ],
222 222
223 223 escaped =
224 224 [ [('%abc def \\', None),
225 225 ('ghi', u_fmt("get_ipython().magic({u}'abc def ghi')")),
226 226 ],
227 227 [('%abc def \\', None),
228 228 ('ghi\\', None),
229 229 (None, u_fmt("get_ipython().magic({u}'abc def ghi')")),
230 230 ],
231 231 ],
232 232
233 233 assign_magic =
234 234 [ [(u'a = %bc de \\', None),
235 235 (u'fg', u_fmt("a = get_ipython().magic({u}'bc de fg')")),
236 236 ],
237 237 [(u'a = %bc de \\', None),
238 238 (u'fg\\', None),
239 239 (None, u_fmt("a = get_ipython().magic({u}'bc de fg')")),
240 240 ],
241 241 ],
242 242
243 243 assign_system =
244 244 [ [(u'a = !bc de \\', None),
245 245 (u'fg', u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
246 246 ],
247 247 [(u'a = !bc de \\', None),
248 248 (u'fg\\', None),
249 249 (None, u_fmt("a = get_ipython().getoutput({u}'bc de fg')")),
250 250 ],
251 251 ],
252 252 )
253 253
254 254
255 255 def test_assign_system():
256 256 tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
257 for example in syntax_ml['assign_system']:
258 transform_checker(example, ipt.assign_from_system)
259 257
260 258 def test_assign_magic():
261 259 tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
262 for example in syntax_ml['assign_magic']:
263 transform_checker(example, ipt.assign_from_magic)
264
265 260
266 261 def test_classic_prompt():
267 262 tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
268 263 for example in syntax_ml['classic_prompt']:
269 264 transform_checker(example, ipt.classic_prompt)
270 265 for example in syntax_ml['multiline_datastructure']:
271 266 transform_checker(example, ipt.classic_prompt)
272 267
273 268
274 269 def test_ipy_prompt():
275 270 tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
276 271 for example in syntax_ml['ipy_prompt']:
277 272 transform_checker(example, ipt.ipy_prompt)
278 273
274 def test_assemble_logical_lines():
275 tests = \
276 [ [(u"a = \\", None),
277 (u"123", u"a = 123"),
278 ],
279 [(u"a = \\", None), # Test resetting when within a multi-line string
280 (u"12 *\\", None),
281 (None, u"a = 12 *"),
282 ],
283 ]
284 for example in tests:
285 transform_checker(example, ipt.assemble_logical_lines)
286
287 def test_assemble_python_lines():
288 tests = \
289 [ [(u"a = '''", None),
290 (u"abc'''", u"a = '''\nabc'''"),
291 ],
292 [(u"a = '''", None), # Test resetting when within a multi-line string
293 (u"def", None),
294 (None, u"a = '''\ndef"),
295 ],
296 [(u"a = [1,", None),
297 (u"2]", u"a = [1,\n2]"),
298 ],
299 [(u"a = [1,", None), # Test resetting when within a multi-line string
300 (u"2,", None),
301 (None, u"a = [1,\n2,"),
302 ],
303 ]
304 for example in tests:
305 transform_checker(example, ipt.assemble_python_lines)
306
307
279 308 def test_help_end():
280 309 tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
281 310
282 311 def test_escaped_noesc():
283 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_noesc'])
312 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
284 313
285 314
286 315 def test_escaped_shell():
287 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_shell'])
316 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
288 317
289 318
290 319 def test_escaped_help():
291 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_help'])
320 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
292 321
293 322
294 323 def test_escaped_magic():
295 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_magic'])
324 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
296 325
297 326
298 327 def test_escaped_quote():
299 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_quote'])
328 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
300 329
301 330
302 331 def test_escaped_quote2():
303 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_quote2'])
332 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
304 333
305 334
306 335 def test_escaped_paren():
307 tt.check_pairs(transform_and_reset(ipt.escaped_transformer), syntax['escaped_paren'])
336 tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
308 337
309 def test_escaped_multiline():
310 for example in syntax_ml['escaped']:
311 transform_checker(example, ipt.escaped_transformer)
312 338
313 339 def test_cellmagic():
314 340 for example in syntax_ml['cellmagic']:
315 341 transform_checker(example, ipt.cellmagic)
316 342
317 343 def test_has_comment():
318 344 tests = [('text', False),
319 345 ('text #comment', True),
320 346 ('text #comment\n', True),
321 347 ('#comment', True),
322 348 ('#comment\n', True),
323 349 ('a = "#string"', False),
324 350 ('a = "#string" # comment', True),
325 351 ('a #comment not "string"', True),
326 352 ]
327 353 tt.check_pairs(ipt.has_comment, tests)
328 354
329 355 @ipt.TokenInputTransformer.wrap
330 356 def decistmt(tokens):
331 357 """Substitute Decimals for floats in a string of statements.
332 358
333 359 Based on an example from the tokenize module docs.
334 360 """
335 361 result = []
336 362 for toknum, tokval, _, _, _ in tokens:
337 363 if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
338 364 for newtok in [
339 365 (tokenize.NAME, 'Decimal'),
340 366 (tokenize.OP, '('),
341 367 (tokenize.STRING, repr(tokval)),
342 368 (tokenize.OP, ')')
343 369 ]:
344 370 yield newtok
345 371 else:
346 372 yield (toknum, tokval)
347 373
348 374
349 375
350 376 def test_token_input_transformer():
351 377 tests = [(u'1.2', u_fmt(u"Decimal ({u}'1.2')")),
352 378 (u'"1.2"', u'"1.2"'),
353 379 ]
354 380 tt.check_pairs(transform_and_reset(decistmt), tests)
355 381 ml_tests = \
356 382 [ [(u"a = 1.2; b = '''x", None),
357 383 (u"y'''", u_fmt(u"a =Decimal ({u}'1.2');b ='''x\ny'''")),
358 384 ],
359 385 [(u"a = [1.2,", None),
360 386 (u"3]", u_fmt(u"a =[Decimal ({u}'1.2'),\n3 ]")),
361 387 ],
362 388 [(u"a = '''foo", None), # Test resetting when within a multi-line string
363 389 (u"bar", None),
364 390 (None, u"a = '''foo\nbar"),
365 391 ],
366 392 ]
367 393 for example in ml_tests:
368 394 transform_checker(example, decistmt)
General Comments 0
You need to be logged in to leave comments. Login now