##// END OF EJS Templates
First pass of input syntax transformation support
Fernando Perez -
Show More
@@ -22,6 +22,9 b' import codeop'
22 import re
22 import re
23 import sys
23 import sys
24
24
25 # IPython modules
26 from IPython.utils.text import make_quoted_expr
27
25 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
26 # Utilities
29 # Utilities
27 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
@@ -419,3 +422,100 b' class InputSplitter(object):'
419
422
420 def _set_source(self):
423 def _set_source(self):
421 self.source = ''.join(self._buffer).encode(self.encoding)
424 self.source = ''.join(self._buffer).encode(self.encoding)
425
426
427 #-----------------------------------------------------------------------------
428 # IPython-specific syntactic support
429 #-----------------------------------------------------------------------------
430
431 # We implement things, as much as possible, as standalone functions that can be
432 # tested and validated in isolation.
433
434 # Each of these uses a regexp, we pre-compile these and keep them close to each
435 # function definition for clarity
436 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
437 r'\s*=\s*!\s*(?P<cmd>.*)')
438
439 def transform_assign_system(line):
440 """Handle the `files = !ls` syntax."""
441 # FIXME: This transforms the line to use %sc, but we've listed that magic
442 # as deprecated. We should then implement this functionality in a
443 # standalone api that we can transform to, without going through a
444 # deprecated magic.
445 m = _assign_system_re.match(line)
446 if m is not None:
447 cmd = m.group('cmd')
448 lhs = m.group('lhs')
449 expr = make_quoted_expr("sc -l = %s" % cmd)
450 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
451 return new_line
452 return line
453
454
455 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
456 r'\s*=\s*%\s*(?P<cmd>.*)')
457
458 def transform_assign_magic(line):
459 """Handle the `a = %who` syntax."""
460 m = _assign_magic_re.match(line)
461 if m is not None:
462 cmd = m.group('cmd')
463 lhs = m.group('lhs')
464 expr = make_quoted_expr(cmd)
465 new_line = '%s = get_ipython().magic(%s)' % (lhs, expr)
466 return new_line
467 return line
468
469
470 _classic_prompt_re = re.compile(r'(^[ \t]*>>> |^[ \t]*\.\.\. )')
471
472 def transform_classic_prompt(line):
473 """Handle inputs that start with '>>> ' syntax."""
474
475 if not line or line.isspace() or line.strip() == '...':
476 # This allows us to recognize multiple input prompts separated by
477 # blank lines and pasted in a single chunk, very common when
478 # pasting doctests or long tutorial passages.
479 return ''
480 m = _classic_prompt_re.match(line)
481 if m:
482 return line[len(m.group(0)):]
483 else:
484 return line
485
486
487 _ipy_prompt_re = re.compile(r'(^[ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
488
489 def transform_ipy_prompt(line):
490 """Handle inputs that start classic IPython prompt syntax."""
491
492 if not line or line.isspace() or line.strip() == '...':
493 # This allows us to recognize multiple input prompts separated by
494 # blank lines and pasted in a single chunk, very common when
495 # pasting doctests or long tutorial passages.
496 return ''
497 m = _ipy_prompt_re.match(line)
498 if m:
499 return line[len(m.group(0)):]
500 else:
501 return line
502
503
504 # Warning, these cannot be changed unless various regular expressions
505 # are updated in a number of places. Not great, but at least we told you.
506 ESC_SHELL = '!'
507 ESC_SH_CAP = '!!'
508 ESC_HELP = '?'
509 ESC_MAGIC = '%'
510 ESC_QUOTE = ','
511 ESC_QUOTE2 = ';'
512 ESC_PAREN = '/'
513
514 class IPythonInputSplitter(InputSplitter):
515 """An input splitter that recognizes all of IPython's special syntax."""
516
517
518 def push(self, lines):
519 """Push one or more lines of IPython input.
520 """
521 return super(IPythonInputSplitter, self).push(lines)
@@ -362,3 +362,50 b' class InteractiveLoopTestCase(unittest.TestCase):'
362 def test_multi(self):
362 def test_multi(self):
363 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
363 self.check_ns(['x =(1+','1+','2)'], dict(x=4))
364
364
365
366 class IPythonInputTestCase(InputSplitterTestCase):
367 def setUp(self):
368 self.isp = isp.IPythonInputSplitter()
369
370
371 # Transformer tests
372 def transform_checker(tests, func):
373 """Utility to loop over test inputs"""
374 for inp, tr in tests:
375 nt.assert_equals(func(inp), tr)
376
377
378 def test_assign_system():
379 tests = [('a =! ls', 'a = get_ipython().magic("sc -l = ls")'),
380 ('b = !ls', 'b = get_ipython().magic("sc -l = ls")'),
381 ('x=1','x=1')]
382 transform_checker(tests, isp.transform_assign_system)
383
384
385 def test_assign_magic():
386 tests = [('a =% who', 'a = get_ipython().magic("who")'),
387 ('b = %who', 'b = get_ipython().magic("who")'),
388 ('x=1','x=1')]
389 transform_checker(tests, isp.transform_assign_magic)
390
391
392 def test_classic_prompt():
393 tests = [('>>> x=1', 'x=1'),
394 ('>>> for i in range(10):','for i in range(10):'),
395 ('... print i',' print i'),
396 ('...', ''),
397 ('x=1','x=1')
398 ]
399 transform_checker(tests, isp.transform_classic_prompt)
400
401
402 def test_ipy_prompt():
403 tests = [('In [1]: x=1', 'x=1'),
404 ('In [24]: for i in range(10):','for i in range(10):'),
405 (' ....: print i',' print i'),
406 (' ....: ', ''),
407 ('x=1', 'x=1'), # normal input is unmodified
408 (' ','') # blank lines are just collapsed
409 ]
410 transform_checker(tests, isp.transform_ipy_prompt)
411
General Comments 0
You need to be logged in to leave comments. Login now