##// END OF EJS Templates
Update inputsplitter to use new input transformers
Thomas Kluyver -
Show More
@@ -69,36 +69,24 b' import ast'
69 69 import codeop
70 70 import re
71 71 import sys
72 import tokenize
73 from StringIO import StringIO
74 72
75 73 # IPython modules
76 74 from IPython.core.splitinput import split_user_input, LineInfo
77 75 from IPython.utils.py3compat import cast_unicode
78
79 #-----------------------------------------------------------------------------
80 # Globals
81 #-----------------------------------------------------------------------------
82
83 # The escape sequences that define the syntax transformations IPython will
84 # apply to user input. These can NOT be just changed here: many regular
85 # expressions and other parts of the code may use their hardcoded values, and
86 # for all intents and purposes they constitute the 'IPython syntax', so they
87 # should be considered fixed.
88
89 ESC_SHELL = '!' # Send line to underlying system shell
90 ESC_SH_CAP = '!!' # Send line to system shell and capture output
91 ESC_HELP = '?' # Find information about object
92 ESC_HELP2 = '??' # Find extra-detailed information about object
93 ESC_MAGIC = '%' # Call magic function
94 ESC_MAGIC2 = '%%' # Call cell-magic function
95 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
96 ESC_QUOTE2 = ';' # Quote all args as a single string, call
97 ESC_PAREN = '/' # Call first argument with rest of line as arguments
98
99 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
100 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
101 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
76 from IPython.core.inputtransformer import (leading_indent,
77 classic_prompt,
78 ipy_prompt,
79 cellmagic,
80 help_end,
81 escaped_transformer,
82 assign_from_magic,
83 assign_from_system,
84 )
85
86 # Temporary!
87 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
88 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
89 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
102 90
103 91 #-----------------------------------------------------------------------------
104 92 # Utilities
@@ -205,29 +193,6 b' def remove_comments(src):'
205 193
206 194 return re.sub('#.*', '', src)
207 195
208 def has_comment(src):
209 """Indicate whether an input line has (i.e. ends in, or is) a comment.
210
211 This uses tokenize, so it can distinguish comments from # inside strings.
212
213 Parameters
214 ----------
215 src : string
216 A single line input string.
217
218 Returns
219 -------
220 Boolean: True if source has a comment.
221 """
222 readline = StringIO(src).readline
223 toktypes = set()
224 try:
225 for t in tokenize.generate_tokens(readline):
226 toktypes.add(t[0])
227 except tokenize.TokenError:
228 pass
229 return(tokenize.COMMENT in toktypes)
230
231 196
232 197 def get_input_encoding():
233 198 """Return the default standard input encoding.
@@ -524,219 +489,15 b' class InputSplitter(object):'
524 489 return u''.join(buffer)
525 490
526 491
527 #-----------------------------------------------------------------------------
528 # Functions and classes for IPython-specific syntactic support
529 #-----------------------------------------------------------------------------
530
531 # The escaped translators ALL receive a line where their own escape has been
532 # stripped. Only '?' is valid at the end of the line, all others can only be
533 # placed at the start.
534
535 # Transformations of the special syntaxes that don't rely on an explicit escape
536 # character but instead on patterns on the input line
537
538 # The core transformations are implemented as standalone functions that can be
539 # tested and validated in isolation. Each of these uses a regexp, we
540 # pre-compile these and keep them close to each function definition for clarity
541
542 _assign_system_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
543 r'\s*=\s*!\s*(?P<cmd>.*)')
544
545 def transform_assign_system(line):
546 """Handle the `files = !ls` syntax."""
547 m = _assign_system_re.match(line)
548 if m is not None:
549 cmd = m.group('cmd')
550 lhs = m.group('lhs')
551 new_line = '%s = get_ipython().getoutput(%r)' % (lhs, cmd)
552 return new_line
553 return line
554
555
556 _assign_magic_re = re.compile(r'(?P<lhs>(\s*)([\w\.]+)((\s*,\s*[\w\.]+)*))'
557 r'\s*=\s*%\s*(?P<cmd>.*)')
558
559 def transform_assign_magic(line):
560 """Handle the `a = %who` syntax."""
561 m = _assign_magic_re.match(line)
562 if m is not None:
563 cmd = m.group('cmd')
564 lhs = m.group('lhs')
565 new_line = '%s = get_ipython().magic(%r)' % (lhs, cmd)
566 return new_line
567 return line
568
569
570 _classic_prompt_re = re.compile(r'^([ \t]*>>> |^[ \t]*\.\.\. )')
571
572 def transform_classic_prompt(line):
573 """Handle inputs that start with '>>> ' syntax."""
574
575 if not line or line.isspace():
576 return line
577 m = _classic_prompt_re.match(line)
578 if m:
579 return line[len(m.group(0)):]
580 else:
581 return line
582
583
584 _ipy_prompt_re = re.compile(r'^([ \t]*In \[\d+\]: |^[ \t]*\ \ \ \.\.\.+: )')
585
586 def transform_ipy_prompt(line):
587 """Handle inputs that start classic IPython prompt syntax."""
588
589 if not line or line.isspace():
590 return line
591 #print 'LINE: %r' % line # dbg
592 m = _ipy_prompt_re.match(line)
593 if m:
594 #print 'MATCH! %r -> %r' % (line, line[len(m.group(0)):]) # dbg
595 return line[len(m.group(0)):]
596 else:
597 return line
598
599
600 def _make_help_call(target, esc, lspace, next_input=None):
601 """Prepares a pinfo(2)/psearch call from a target name and the escape
602 (i.e. ? or ??)"""
603 method = 'pinfo2' if esc == '??' \
604 else 'psearch' if '*' in target \
605 else 'pinfo'
606 arg = " ".join([method, target])
607 if next_input is None:
608 return '%sget_ipython().magic(%r)' % (lspace, arg)
609 else:
610 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
611 (lspace, next_input, arg)
612
613
614 _initial_space_re = re.compile(r'\s*')
615
616 _help_end_re = re.compile(r"""(%{0,2}
617 [a-zA-Z_*][\w*]* # Variable name
618 (\.[a-zA-Z_*][\w*]*)* # .etc.etc
619 )
620 (\?\??)$ # ? or ??""",
621 re.VERBOSE)
622
623
624 def transform_help_end(line):
625 """Translate lines with ?/?? at the end"""
626 m = _help_end_re.search(line)
627 if m is None or has_comment(line):
628 return line
629 target = m.group(1)
630 esc = m.group(3)
631 lspace = _initial_space_re.match(line).group(0)
632
633 # If we're mid-command, put it back on the next prompt for the user.
634 next_input = line.rstrip('?') if line.strip() != m.group(0) else None
635
636 return _make_help_call(target, esc, lspace, next_input)
637
638
639 class EscapedTransformer(object):
640 """Class to transform lines that are explicitly escaped out."""
641
642 def __init__(self):
643 tr = { ESC_SHELL : self._tr_system,
644 ESC_SH_CAP : self._tr_system2,
645 ESC_HELP : self._tr_help,
646 ESC_HELP2 : self._tr_help,
647 ESC_MAGIC : self._tr_magic,
648 ESC_QUOTE : self._tr_quote,
649 ESC_QUOTE2 : self._tr_quote2,
650 ESC_PAREN : self._tr_paren }
651 self.tr = tr
652
653 # Support for syntax transformations that use explicit escapes typed by the
654 # user at the beginning of a line
655 @staticmethod
656 def _tr_system(line_info):
657 "Translate lines escaped with: !"
658 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
659 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
660
661 @staticmethod
662 def _tr_system2(line_info):
663 "Translate lines escaped with: !!"
664 cmd = line_info.line.lstrip()[2:]
665 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
666
667 @staticmethod
668 def _tr_help(line_info):
669 "Translate lines escaped with: ?/??"
670 # A naked help line should just fire the intro help screen
671 if not line_info.line[1:]:
672 return 'get_ipython().show_usage()'
673
674 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
675
676 @staticmethod
677 def _tr_magic(line_info):
678 "Translate lines escaped with: %"
679 tpl = '%sget_ipython().magic(%r)'
680 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
681 return tpl % (line_info.pre, cmd)
682
683 @staticmethod
684 def _tr_quote(line_info):
685 "Translate lines escaped with: ,"
686 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
687 '", "'.join(line_info.the_rest.split()) )
688
689 @staticmethod
690 def _tr_quote2(line_info):
691 "Translate lines escaped with: ;"
692 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
693 line_info.the_rest)
694
695 @staticmethod
696 def _tr_paren(line_info):
697 "Translate lines escaped with: /"
698 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
699 ", ".join(line_info.the_rest.split()))
700
701 def __call__(self, line):
702 """Class to transform lines that are explicitly escaped out.
703
704 This calls the above _tr_* static methods for the actual line
705 translations."""
706
707 # Empty lines just get returned unmodified
708 if not line or line.isspace():
709 return line
710
711 # Get line endpoints, where the escapes can be
712 line_info = LineInfo(line)
713
714 if not line_info.esc in self.tr:
715 # If we don't recognize the escape, don't modify the line
716 return line
717
718 return self.tr[line_info.esc](line_info)
719
720
721 # A function-looking object to be used by the rest of the code. The purpose of
722 # the class in this case is to organize related functionality, more than to
723 # manage state.
724 transform_escaped = EscapedTransformer()
725
726
727 492 class IPythonInputSplitter(InputSplitter):
728 493 """An input splitter that recognizes all of IPython's special syntax."""
729 494
730 495 # String with raw, untransformed input.
731 496 source_raw = ''
732
733 # Flag to track when we're in the middle of processing a cell magic, since
734 # the logic has to change. In that case, we apply no transformations at
735 # all.
736 processing_cell_magic = False
737
738 # Storage for all blocks of input that make up a cell magic
739 cell_magic_parts = []
497
498 # Flag to track when a transformer has stored input that it hasn't given
499 # back yet.
500 transformer_accumulating = False
740 501
741 502 # Private attributes
742 503
@@ -747,14 +508,22 b' class IPythonInputSplitter(InputSplitter):'
747 508 super(IPythonInputSplitter, self).__init__(input_mode)
748 509 self._buffer_raw = []
749 510 self._validate = True
511 self.transforms = [leading_indent,
512 classic_prompt,
513 ipy_prompt,
514 cellmagic,
515 help_end,
516 escaped_transformer,
517 assign_from_magic,
518 assign_from_system,
519 ]
750 520
751 521 def reset(self):
752 522 """Reset the input buffer and associated state."""
753 523 super(IPythonInputSplitter, self).reset()
754 524 self._buffer_raw[:] = []
755 525 self.source_raw = ''
756 self.cell_magic_parts = []
757 self.processing_cell_magic = False
526 self.transformer_accumulating = False
758 527
759 528 def source_raw_reset(self):
760 529 """Return input and raw source and perform a full reset.
@@ -765,56 +534,11 b' class IPythonInputSplitter(InputSplitter):'
765 534 return out, out_r
766 535
767 536 def push_accepts_more(self):
768 if self.processing_cell_magic:
769 return not self._is_complete
537 if self.transformer_accumulating:
538 return True
770 539 else:
771 540 return super(IPythonInputSplitter, self).push_accepts_more()
772 541
773 def _handle_cell_magic(self, lines):
774 """Process lines when they start with %%, which marks cell magics.
775 """
776 self.processing_cell_magic = True
777 first, _, body = lines.partition('\n')
778 magic_name, _, line = first.partition(' ')
779 magic_name = magic_name.lstrip(ESC_MAGIC)
780 # We store the body of the cell and create a call to a method that
781 # will use this stored value. This is ugly, but it's a first cut to
782 # get it all working, as right now changing the return API of our
783 # methods would require major refactoring.
784 self.cell_magic_parts = [body]
785 tpl = 'get_ipython()._run_cached_cell_magic(%r, %r)'
786 tlines = tpl % (magic_name, line)
787 self._store(tlines)
788 self._store(lines, self._buffer_raw, 'source_raw')
789 # We can actually choose whether to allow for single blank lines here
790 # during input for clients that use cell mode to decide when to stop
791 # pushing input (currently only the Qt console).
792 # My first implementation did that, and then I realized it wasn't
793 # consistent with the terminal behavior, so I've reverted it to one
794 # line. But I'm leaving it here so we can easily test both behaviors,
795 # I kind of liked having full blank lines allowed in the cell magics...
796 #self._is_complete = last_two_blanks(lines)
797 self._is_complete = last_blank(lines)
798 return self._is_complete
799
800 def _line_mode_cell_append(self, lines):
801 """Append new content for a cell magic in line mode.
802 """
803 # Only store the raw input. Lines beyond the first one are only only
804 # stored for history purposes; for execution the caller will grab the
805 # magic pieces from cell_magic_parts and will assemble the cell body
806 self._store(lines, self._buffer_raw, 'source_raw')
807 self.cell_magic_parts.append(lines)
808 # Find out if the last stored block has a whitespace line as its
809 # last line and also this line is whitespace, case in which we're
810 # done (two contiguous blank lines signal termination). Note that
811 # the storage logic *enforces* that every stored block is
812 # newline-terminated, so we grab everything but the last character
813 # so we can have the body of the block alone.
814 last_block = self.cell_magic_parts[-1]
815 self._is_complete = last_blank(last_block) and lines.isspace()
816 return self._is_complete
817
818 542 def transform_cell(self, cell):
819 543 """Process and translate a cell of input.
820 544 """
@@ -851,24 +575,10 b' class IPythonInputSplitter(InputSplitter):'
851 575 # We must ensure all input is pure unicode
852 576 lines = cast_unicode(lines, self.encoding)
853 577
854 # If the entire input block is a cell magic, return after handling it
855 # as the rest of the transformation logic should be skipped.
856 if lines.startswith('%%') and not \
857 (len(lines.splitlines()) == 1 and lines.strip().endswith('?')):
858 return self._handle_cell_magic(lines)
859
860 # In line mode, a cell magic can arrive in separate pieces
861 if self.input_mode == 'line' and self.processing_cell_magic:
862 return self._line_mode_cell_append(lines)
863
864 578 # The rest of the processing is for 'normal' content, i.e. IPython
865 579 # source that we process through our transformations pipeline.
866 580 lines_list = lines.splitlines()
867 581
868 transforms = [transform_ipy_prompt, transform_classic_prompt,
869 transform_help_end, transform_escaped,
870 transform_assign_system, transform_assign_magic]
871
872 582 # Transform logic
873 583 #
874 584 # We only apply the line transformers to the input if we have either no
@@ -901,16 +611,24 b' class IPythonInputSplitter(InputSplitter):'
901 611 self._store(lines, self._buffer_raw, 'source_raw')
902 612
903 613 try:
904 push = super(IPythonInputSplitter, self).push
905 buf = self._buffer
906 614 for line in lines_list:
907 if self._is_complete or not buf or \
908 (buf and buf[-1].rstrip().endswith((':', ','))):
909 for f in transforms:
910 line = f(line)
911
912 out = push(line)
615 out = self.push_line(line)
913 616 finally:
914 617 if changed_input_mode:
915 618 self.input_mode = saved_input_mode
619
916 620 return out
621
622 def push_line(self, line):
623 buf = self._buffer
624 not_in_string = self._is_complete or not buf or \
625 (buf and buf[-1].rstrip().endswith((':', ',')))
626 for transformer in self.transforms:
627 if not_in_string or transformer.look_in_string:
628 line = transformer.push(line)
629 if line is None:
630 self.transformer_accumulating = True
631 return False
632
633 self.transformer_accumulating = False
634 return super(IPythonInputSplitter, self).push(line)
@@ -1,11 +1,35 b''
1 1 import abc
2 2 import re
3 from StringIO import StringIO
4 import tokenize
3 5
4 6 from IPython.core.splitinput import split_user_input, LineInfo
5 from IPython.core.inputsplitter import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
6 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
7 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN)
8 from IPython.core.inputsplitter import EscapedTransformer, _make_help_call, has_comment
7
8 #-----------------------------------------------------------------------------
9 # Globals
10 #-----------------------------------------------------------------------------
11
12 # The escape sequences that define the syntax transformations IPython will
13 # apply to user input. These can NOT be just changed here: many regular
14 # expressions and other parts of the code may use their hardcoded values, and
15 # for all intents and purposes they constitute the 'IPython syntax', so they
16 # should be considered fixed.
17
18 ESC_SHELL = '!' # Send line to underlying system shell
19 ESC_SH_CAP = '!!' # Send line to system shell and capture output
20 ESC_HELP = '?' # Find information about object
21 ESC_HELP2 = '??' # Find extra-detailed information about object
22 ESC_MAGIC = '%' # Call magic function
23 ESC_MAGIC2 = '%%' # Call cell-magic function
24 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
25 ESC_QUOTE2 = ';' # Quote all args as a single string, call
26 ESC_PAREN = '/' # Call first argument with rest of line as arguments
27
28 ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
29 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
30 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
31
32
9 33
10 34 class InputTransformer(object):
11 35 __metaclass__ = abc.ABCMeta
@@ -44,16 +68,81 b' class CoroutineInputTransformer(InputTransformer):'
44 68 def reset(self):
45 69 self.coro.send(None)
46 70
71
72 # Utilities
73 def _make_help_call(target, esc, lspace, next_input=None):
74 """Prepares a pinfo(2)/psearch call from a target name and the escape
75 (i.e. ? or ??)"""
76 method = 'pinfo2' if esc == '??' \
77 else 'psearch' if '*' in target \
78 else 'pinfo'
79 arg = " ".join([method, target])
80 if next_input is None:
81 return '%sget_ipython().magic(%r)' % (lspace, arg)
82 else:
83 return '%sget_ipython().set_next_input(%r);get_ipython().magic(%r)' % \
84 (lspace, next_input, arg)
85
47 86 @CoroutineInputTransformer
48 87 def escaped_transformer():
49 et = EscapedTransformer()
88 """Translate lines beginning with one of IPython's escape characters."""
89
90 # These define the transformations for the different escape characters.
91 def _tr_system(line_info):
92 "Translate lines escaped with: !"
93 cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
94 return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
95
96 def _tr_system2(line_info):
97 "Translate lines escaped with: !!"
98 cmd = line_info.line.lstrip()[2:]
99 return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
100
101 def _tr_help(line_info):
102 "Translate lines escaped with: ?/??"
103 # A naked help line should just fire the intro help screen
104 if not line_info.line[1:]:
105 return 'get_ipython().show_usage()'
106
107 return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
108
109 def _tr_magic(line_info):
110 "Translate lines escaped with: %"
111 tpl = '%sget_ipython().magic(%r)'
112 cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
113 return tpl % (line_info.pre, cmd)
114
115 def _tr_quote(line_info):
116 "Translate lines escaped with: ,"
117 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
118 '", "'.join(line_info.the_rest.split()) )
119
120 def _tr_quote2(line_info):
121 "Translate lines escaped with: ;"
122 return '%s%s("%s")' % (line_info.pre, line_info.ifun,
123 line_info.the_rest)
124
125 def _tr_paren(line_info):
126 "Translate lines escaped with: /"
127 return '%s%s(%s)' % (line_info.pre, line_info.ifun,
128 ", ".join(line_info.the_rest.split()))
129
130 tr = { ESC_SHELL : _tr_system,
131 ESC_SH_CAP : _tr_system2,
132 ESC_HELP : _tr_help,
133 ESC_HELP2 : _tr_help,
134 ESC_MAGIC : _tr_magic,
135 ESC_QUOTE : _tr_quote,
136 ESC_QUOTE2 : _tr_quote2,
137 ESC_PAREN : _tr_paren }
138
50 139 line = ''
51 140 while True:
52 141 line = (yield line)
53 142 if not line or line.isspace():
54 143 continue
55 144 lineinf = LineInfo(line)
56 if lineinf.esc not in et.tr:
145 if lineinf.esc not in tr:
57 146 continue
58 147
59 148 parts = []
@@ -65,7 +154,7 b' def escaped_transformer():'
65 154
66 155 # Output
67 156 lineinf = LineInfo(' '.join(parts))
68 line = et.tr[lineinf.esc](lineinf)
157 line = tr[lineinf.esc](lineinf)
69 158
70 159 _initial_space_re = re.compile(r'\s*')
71 160
@@ -76,8 +165,31 b' _help_end_re = re.compile(r"""(%{0,2}'
76 165 (\?\??)$ # ? or ??""",
77 166 re.VERBOSE)
78 167
168 def has_comment(src):
169 """Indicate whether an input line has (i.e. ends in, or is) a comment.
170
171 This uses tokenize, so it can distinguish comments from # inside strings.
172
173 Parameters
174 ----------
175 src : string
176 A single line input string.
177
178 Returns
179 -------
180 Boolean: True if source has a comment.
181 """
182 readline = StringIO(src).readline
183 toktypes = set()
184 try:
185 for t in tokenize.generate_tokens(readline):
186 toktypes.add(t[0])
187 except tokenize.TokenError:
188 pass
189 return(tokenize.COMMENT in toktypes)
190
79 191 @StatelessInputTransformer
80 def transform_help_end(line):
192 def help_end(line):
81 193 """Translate lines with ?/?? at the end"""
82 194 m = _help_end_re.search(line)
83 195 if m is None or has_comment(line):
@@ -2591,13 +2591,6 b' class InteractiveShell(SingletonConfigurable):'
2591 2591
2592 2592 self.input_splitter.push(raw_cell)
2593 2593
2594 # Check for cell magics, which leave state behind. This interface is
2595 # ugly, we need to do something cleaner later... Now the logic is
2596 # simply that the input_splitter remembers if there was a cell magic,
2597 # and in that case we grab the cell body.
2598 if self.input_splitter.cell_magic_parts:
2599 self._current_cell_magic_body = \
2600 ''.join(self.input_splitter.cell_magic_parts)
2601 2594 cell = self.input_splitter.source_reset()
2602 2595
2603 2596 # Our own compiler remembers the __future__ environment. If we want to
@@ -37,7 +37,7 b' def test_transform_escaped():'
37 37 tt.check_pairs(wrap_transform(inputtransformer.escaped_transformer), esctransform_tests)
38 38
39 39 def endhelp_test():
40 tt.check_pairs(inputtransformer.transform_help_end.push, syntax['end_help'])
40 tt.check_pairs(inputtransformer.help_end.push, syntax['end_help'])
41 41
42 42 classic_prompt_tests = [
43 43 (['>>> a=1'], ['a=1']),
General Comments 0
You need to be logged in to leave comments. Login now