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