##// END OF EJS Templates
Final cleanups responding to Brian's code review....
Fernando Perez -
Show More
@@ -5,9 +5,30 b' input from either interactive, line-by-line environments or block-based ones,'
5 into standalone blocks that can be executed by Python as 'single' statements
5 into standalone blocks that can be executed by Python as 'single' statements
6 (thus triggering sys.displayhook).
6 (thus triggering sys.displayhook).
7
7
8 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
9 with full support for the extended IPython syntax (magics, system calls, etc).
10
8 For more details, see the class docstring below.
11 For more details, see the class docstring below.
9
12
13 ToDo
14 ----
15
16 - Naming cleanups. The tr_* names aren't the most elegant, though now they are
17 at least just attributes of a class so not really very exposed.
18
19 - Think about the best way to support dynamic things: automagic, autocall,
20 macros, etc.
21
22 - Think of a better heuristic for the application of the transforms in
23 IPythonInputSplitter.push() than looking at the buffer ending in ':'. Idea:
24 track indentation change events (indent, dedent, nothing) and apply them only
25 if the indentation went up, but not otherwise.
26
27 - Think of the cleanest way for supporting user-specified transformations (the
28 user prefilters we had before).
29
10 Authors
30 Authors
31 -------
11
32
12 * Fernando Perez
33 * Fernando Perez
13 * Brian Granger
34 * Brian Granger
@@ -513,11 +534,11 b' def split_user_input(line):'
513 else:
534 else:
514 # print "match failed for line '%s'" % line
535 # print "match failed for line '%s'" % line
515 try:
536 try:
516 fpart, rest = line.split(None,1)
537 fpart, rest = line.split(None, 1)
517 except ValueError:
538 except ValueError:
518 # print "split failed for line '%s'" % line
539 # print "split failed for line '%s'" % line
519 fpart, rest = line,''
540 fpart, rest = line,''
520 lspace = re.match('^(\s*)(.*)',line).groups()[0]
541 lspace = re.match('^(\s*)(.*)', line).groups()[0]
521 esc = ''
542 esc = ''
522
543
523 # fpart has to be a valid python identifier, so it better be only pure
544 # fpart has to be a valid python identifier, so it better be only pure
@@ -557,9 +578,6 b' class LineInfo(object):'
557 The initial esc character (or characters, for double-char escapes like
578 The initial esc character (or characters, for double-char escapes like
558 '??' or '!!').
579 '??' or '!!').
559
580
560 pre_char
561 The escape character(s) in esc or the empty string if there isn't one.
562
563 fpart
581 fpart
564 The 'function part', which is basically the maximal initial sequence
582 The 'function part', which is basically the maximal initial sequence
565 of valid python identifiers and the '.' character. This is what is
583 of valid python identifiers and the '.' character. This is what is
@@ -648,131 +666,116 b' def transform_ipy_prompt(line):'
648 return line
666 return line
649
667
650
668
651 def transform_unescaped(line):
669 class EscapedTransformer(object):
652 """Transform lines that are explicitly escaped out.
670 """Class to transform lines that are explicitly escaped out."""
653
654 This calls to the above transform_* functions for the actual line
655 translations.
656
671
657 Parameters
672 def __init__(self):
658 ----------
673 tr = { ESC_SHELL : self.tr_system,
659 line : str
674 ESC_SH_CAP : self.tr_system2,
660 A single line of input to be transformed.
675 ESC_HELP : self.tr_help,
661
676 ESC_HELP2 : self.tr_help,
662 Returns
677 ESC_MAGIC : self.tr_magic,
663 -------
678 ESC_QUOTE : self.tr_quote,
664 new_line : str
679 ESC_QUOTE2 : self.tr_quote2,
665 Transformed line, which may be identical to the original."""
680 ESC_PAREN : self.tr_paren }
681 self.tr = tr
682
683 # Support for syntax transformations that use explicit escapes typed by the
684 # user at the beginning of a line
685 @staticmethod
686 def tr_system(line_info):
687 "Translate lines escaped with: !"
688 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
689 return '%sget_ipython().system(%s)' % (line_info.lspace,
690 make_quoted_expr(cmd))
691
692 @staticmethod
693 def tr_system2(line_info):
694 "Translate lines escaped with: !!"
695 cmd = line_info.line.lstrip()[2:]
696 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
697 make_quoted_expr(cmd))
698
699 @staticmethod
700 def tr_help(line_info):
701 "Translate lines escaped with: ?/??"
702 # A naked help line should just fire the intro help screen
703 if not line_info.line[1:]:
704 return 'get_ipython().show_usage()'
705
706 # There may be one or two '?' at the end, move them to the front so that
707 # the rest of the logic can assume escapes are at the start
708 line = line_info.line
709 if line.endswith('?'):
710 line = line[-1] + line[:-1]
711 if line.endswith('?'):
712 line = line[-1] + line[:-1]
713 line_info = LineInfo(line)
714
715 # From here on, simply choose which level of detail to get.
716 if line_info.esc == '?':
717 pinfo = 'pinfo'
718 elif line_info.esc == '??':
719 pinfo = 'pinfo2'
720
721 tpl = '%sget_ipython().magic("%s %s")'
722 return tpl % (line_info.lspace, pinfo,
723 ' '.join([line_info.fpart, line_info.rest]).strip())
724
725 @staticmethod
726 def tr_magic(line_info):
727 "Translate lines escaped with: %"
728 tpl = '%sget_ipython().magic(%s)'
729 cmd = make_quoted_expr(' '.join([line_info.fpart,
730 line_info.rest])).strip()
731 return tpl % (line_info.lspace, cmd)
732
733 @staticmethod
734 def tr_quote(line_info):
735 "Translate lines escaped with: ,"
736 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
737 '", "'.join(line_info.rest.split()) )
738
739 @staticmethod
740 def tr_quote2(line_info):
741 "Translate lines escaped with: ;"
742 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
743 line_info.rest)
744
745 @staticmethod
746 def tr_paren(line_info):
747 "Translate lines escaped with: /"
748 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
749 ", ".join(line_info.rest.split()))
750
751 def __call__(self, line):
752 """Class to transform lines that are explicitly escaped out.
753
754 This calls the above tr_* static methods for the actual line
755 translations."""
756
757 # Empty lines just get returned unmodified
758 if not line or line.isspace():
759 return line
666
760
667 if not line or line.isspace():
761 # Get line endpoints, where the escapes can be
668 return line
762 line_info = LineInfo(line)
669
763
670 new_line = line
764 # If the escape is not at the start, only '?' needs to be special-cased.
671 for f in [transform_assign_system, transform_assign_magic,
765 # All other escapes are only valid at the start
672 transform_classic_prompt, transform_ipy_prompt ] :
766 if not line_info.esc in self.tr:
673 new_line = f(new_line)
767 if line.endswith(ESC_HELP):
674 return new_line
768 return self.tr_help(line_info)
675
769 else:
676 # Support for syntax transformations that use explicit escapes typed by the
770 # If we don't recognize the escape, don't modify the line
677 # user at the beginning of a line
771 return line
678
679 def tr_system(line_info):
680 "Translate lines escaped with: !"
681 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
682 return '%sget_ipython().system(%s)' % (line_info.lspace,
683 make_quoted_expr(cmd))
684
685
686 def tr_system2(line_info):
687 "Translate lines escaped with: !!"
688 cmd = line_info.line.lstrip()[2:]
689 return '%sget_ipython().getoutput(%s)' % (line_info.lspace,
690 make_quoted_expr(cmd))
691
692
693 def tr_help(line_info):
694 "Translate lines escaped with: ?/??"
695 # A naked help line should just fire the intro help screen
696 if not line_info.line[1:]:
697 return 'get_ipython().show_usage()'
698
699 # There may be one or two '?' at the end, move them to the front so that
700 # the rest of the logic can assume escapes are at the start
701 line = line_info.line
702 if line.endswith('?'):
703 line = line[-1] + line[:-1]
704 if line.endswith('?'):
705 line = line[-1] + line[:-1]
706 line_info = LineInfo(line)
707
708 # From here on, simply choose which level of detail to get.
709 if line_info.esc == '?':
710 pinfo = 'pinfo'
711 elif line_info.esc == '??':
712 pinfo = 'pinfo2'
713
714 tpl = '%sget_ipython().magic("%s %s")'
715 return tpl % (line_info.lspace, pinfo,
716 ' '.join([line_info.fpart, line_info.rest]).strip())
717
718
719 def tr_magic(line_info):
720 "Translate lines escaped with: %"
721 tpl = '%sget_ipython().magic(%s)'
722 cmd = make_quoted_expr(' '.join([line_info.fpart,
723 line_info.rest])).strip()
724 return tpl % (line_info.lspace, cmd)
725
726
727 def tr_quote(line_info):
728 "Translate lines escaped with: ,"
729 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
730 '", "'.join(line_info.rest.split()) )
731
732
733 def tr_quote2(line_info):
734 "Translate lines escaped with: ;"
735 return '%s%s("%s")' % (line_info.lspace, line_info.fpart,
736 line_info.rest)
737
738
739 def tr_paren(line_info):
740 "Translate lines escaped with: /"
741 return '%s%s(%s)' % (line_info.lspace, line_info.fpart,
742 ", ".join(line_info.rest.split()))
743
744
745 def transform_escaped(line):
746 """Transform lines that are explicitly escaped out.
747
748 This calls to the above tr_* functions for the actual line translations."""
749
750 tr = { ESC_SHELL : tr_system,
751 ESC_SH_CAP : tr_system2,
752 ESC_HELP : tr_help,
753 ESC_HELP2 : tr_help,
754 ESC_MAGIC : tr_magic,
755 ESC_QUOTE : tr_quote,
756 ESC_QUOTE2 : tr_quote2,
757 ESC_PAREN : tr_paren }
758
759 # Empty lines just get returned unmodified
760 if not line or line.isspace():
761 return line
762
772
763 # Get line endpoints, where the escapes can be
773 return self.tr[line_info.esc](line_info)
764 line_info = LineInfo(line)
765
774
766 # If the escape is not at the start, only '?' needs to be special-cased.
775 # A function-looking object to be used by the rest of the code. The purpose of
767 # All other escapes are only valid at the start
776 # the class in this case is to organize related functionality, more than to
768 if not line_info.esc in tr:
777 # manage state.
769 if line.endswith(ESC_HELP):
778 transform_escaped = EscapedTransformer()
770 return tr_help(line_info)
771 else:
772 # If we don't recognize the escape, don't modify the line
773 return line
774
775 return tr[line_info.esc](line_info)
776
779
777
780
778 class IPythonInputSplitter(InputSplitter):
781 class IPythonInputSplitter(InputSplitter):
@@ -781,18 +784,34 b' class IPythonInputSplitter(InputSplitter):'
781 def push(self, lines):
784 def push(self, lines):
782 """Push one or more lines of IPython input.
785 """Push one or more lines of IPython input.
783 """
786 """
787 if not lines:
788 return super(IPythonInputSplitter, self).push(lines)
789
790 lines_list = lines.splitlines()
791
792 transforms = [transform_escaped, transform_assign_system,
793 transform_assign_magic, transform_ipy_prompt,
794 transform_classic_prompt]
795
796 # Transform logic
797 #
784 # We only apply the line transformers to the input if we have either no
798 # We only apply the line transformers to the input if we have either no
785 # input yet, or complete input. This prevents the accidental
799 # input yet, or complete input, or if the last line of the buffer ends
800 # with ':' (opening an indented block). This prevents the accidental
786 # transformation of escapes inside multiline expressions like
801 # transformation of escapes inside multiline expressions like
787 # triple-quoted strings or parenthesized expressions.
802 # triple-quoted strings or parenthesized expressions.
788 lines_list = lines.splitlines()
803 #
789 if self._is_complete or not self._buffer:
804 # The last heuristic, while ugly, ensures that the first line of an
805 # indented block is correctly transformed.
806 #
807 # FIXME: try to find a cleaner approach for this last bit.
808
809 for line in lines_list:
810 if self._is_complete or not self._buffer or \
811 (self._buffer and self._buffer[-1].rstrip().endswith(':')):
812 for f in transforms:
813 line = f(line)
790
814
791 new_list = map(transform_escaped, lines_list)
815 out = super(IPythonInputSplitter, self).push(line)
792 else:
793 new_list = lines_list
794
816
795 # Now apply the unescaped transformations to each input line
817 return out
796 new_list = map(transform_unescaped, new_list)
797 newlines = '\n'.join(new_list)
798 return super(IPythonInputSplitter, self).push(newlines)
@@ -593,16 +593,14 b" if __name__ == '__main__':"
593 # picked up by any test suite. Useful mostly for illustration and during
593 # picked up by any test suite. Useful mostly for illustration and during
594 # development.
594 # development.
595 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
595 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
596
596
597 # configure here the syntax to use, prompt and whether to autoindent
597 #isp, start_prompt = InputSplitter(), '>>> '
598 #isp, start_prompt = InputSplitter(), '>>> '
598 isp, start_prompt = IPythonInputSplitter(), 'In> '
599 isp, start_prompt = IPythonInputSplitter(), 'In> '
599
600
600 autoindent = True
601 autoindent = True
601 #autoindent = False
602 #autoindent = False
602
603
603 # In practice, this input loop would be wrapped in an outside loop to read
604 # input indefinitely, until some exit/quit command was issued. Here we
605 # only illustrate the basic inner loop.
606 try:
604 try:
607 while True:
605 while True:
608 prompt = start_prompt
606 prompt = start_prompt
@@ -618,6 +616,6 b" if __name__ == '__main__':"
618 # Here we just return input so we can use it in a test suite, but a
616 # Here we just return input so we can use it in a test suite, but a
619 # real interpreter would instead send it for execution somewhere.
617 # real interpreter would instead send it for execution somewhere.
620 src = isp.source_reset()
618 src = isp.source_reset()
621 print 'Input source was:\n', src # dbg
619 print 'Input source was:\n', src
622 except EOFError:
620 except EOFError:
623 print 'Bye'
621 print 'Bye'
General Comments 0
You need to be logged in to leave comments. Login now