Show More
@@ -142,6 +142,21 b' def num_ini_spaces(s):' | |||
|
142 | 142 | return 0 |
|
143 | 143 | |
|
144 | 144 | |
|
145 | def last_blank(src): | |
|
146 | """Determine if the input source ends in a blank. | |
|
147 | ||
|
148 | A blank is either a newline or a line consisting of whitespace. | |
|
149 | ||
|
150 | Parameters | |
|
151 | ---------- | |
|
152 | src : string | |
|
153 | A single or multiline string. | |
|
154 | """ | |
|
155 | if not src: return False | |
|
156 | ll = src.splitlines()[-1] | |
|
157 | return (ll == '') or ll.isspace() | |
|
158 | ||
|
159 | ||
|
145 | 160 | def remove_comments(src): |
|
146 | 161 | """Remove all comments from input source. |
|
147 | 162 | |
@@ -685,23 +700,27 b' class IPythonInputSplitter(InputSplitter):' | |||
|
685 | 700 | # String with raw, untransformed input. |
|
686 | 701 | source_raw = '' |
|
687 | 702 | |
|
688 |
cell_magic_ |
|
|
703 | cell_magic_parts = [] | |
|
704 | ||
|
705 | cell_magic_mode = False | |
|
689 | 706 | |
|
690 | 707 | # Private attributes |
|
691 | ||
|
708 | ||
|
692 | 709 | # List with lines of raw input accumulated so far. |
|
693 | 710 | _buffer_raw = None |
|
694 | 711 | |
|
695 | 712 | def __init__(self, input_mode=None): |
|
696 | 713 | super(IPythonInputSplitter, self).__init__(input_mode) |
|
697 | 714 | self._buffer_raw = [] |
|
715 | self._validate = True | |
|
698 | 716 | |
|
699 | 717 | def reset(self): |
|
700 | 718 | """Reset the input buffer and associated state.""" |
|
701 | 719 | super(IPythonInputSplitter, self).reset() |
|
702 | 720 | self._buffer_raw[:] = [] |
|
703 | 721 | self.source_raw = '' |
|
704 |
self.cell_magic_ |
|
|
722 | self.cell_magic_parts = [] | |
|
723 | self.cell_magic_mode = False | |
|
705 | 724 | |
|
706 | 725 | def source_raw_reset(self): |
|
707 | 726 | """Return input and raw source and perform a full reset. |
@@ -711,6 +730,96 b' class IPythonInputSplitter(InputSplitter):' | |||
|
711 | 730 | self.reset() |
|
712 | 731 | return out, out_r |
|
713 | 732 | |
|
733 | def push_accepts_more(self): | |
|
734 | if self.cell_magic_mode: | |
|
735 | return not self._is_complete | |
|
736 | else: | |
|
737 | return super(IPythonInputSplitter, self).push_accepts_more() | |
|
738 | ||
|
739 | def _push_line_mode(self, lines): | |
|
740 | """Push in line mode. | |
|
741 | ||
|
742 | This means that we only get individual 'lines' with each call, though | |
|
743 | in practice each input may be multiline. But this is in contrast to | |
|
744 | cell mode, which feeds the entirety of the cell from the start with | |
|
745 | each call. | |
|
746 | """ | |
|
747 | # cell magic support | |
|
748 | #print('#'*10) | |
|
749 | #print(lines+'\n---') # dbg | |
|
750 | #print (repr(lines)+'\n+++') | |
|
751 | #print('raw', self._buffer_raw, 'validate', self.cell_magic_mode) | |
|
752 | # Only trigger this block if we're at a 'fresh' pumping start. | |
|
753 | if lines.startswith('%%') and (not self.cell_magic_mode) and \ | |
|
754 | not self._buffer_raw: | |
|
755 | # Cell magics bypass all further transformations | |
|
756 | self.cell_magic_mode = True | |
|
757 | first, _, body = lines.partition('\n') | |
|
758 | magic_name, _, line = first.partition(' ') | |
|
759 | magic_name = magic_name.lstrip(ESC_MAGIC) | |
|
760 | # We store the body of the cell and create a call to a method that | |
|
761 | # will use this stored value. This is ugly, but it's a first cut to | |
|
762 | # get it all working, as right now changing the return API of our | |
|
763 | # methods would require major refactoring. | |
|
764 | self.cell_magic_parts = [body] | |
|
765 | tpl = 'get_ipython()._cell_magic(%r, %r)' | |
|
766 | tlines = tpl % (magic_name, line) | |
|
767 | self._store(tlines) | |
|
768 | self._store(lines, self._buffer_raw, 'source_raw') | |
|
769 | self._is_complete = False | |
|
770 | return False | |
|
771 | ||
|
772 | if self.cell_magic_mode: | |
|
773 | # Find out if the last stored block has a whitespace line as its | |
|
774 | # last line and also this line is whitespace, case in which we're | |
|
775 | # done (two contiguous blank lines signal termination). Note that | |
|
776 | # the storage logic *enforces* that every stored block is | |
|
777 | # newline-terminated, so we grab everything but the last character | |
|
778 | # so we can have the body of the block alone. | |
|
779 | last_block = self.cell_magic_parts[-1] | |
|
780 | self._is_complete = last_blank(last_block) and lines.isspace() | |
|
781 | # Only store the raw input. For lines beyond the first one, we | |
|
782 | # only store them for history purposes, and for execution we want | |
|
783 | # the caller to only receive the _cell_magic() call. | |
|
784 | self._store(lines, self._buffer_raw, 'source_raw') | |
|
785 | self.cell_magic_parts.append(lines) | |
|
786 | return self._is_complete | |
|
787 | ||
|
788 | lines_list = lines.splitlines() | |
|
789 | ||
|
790 | transforms = [transform_ipy_prompt, transform_classic_prompt, | |
|
791 | transform_help_end, transform_escaped, | |
|
792 | transform_assign_system, transform_assign_magic] | |
|
793 | ||
|
794 | # Transform logic | |
|
795 | # | |
|
796 | # We only apply the line transformers to the input if we have either no | |
|
797 | # input yet, or complete input, or if the last line of the buffer ends | |
|
798 | # with ':' (opening an indented block). This prevents the accidental | |
|
799 | # transformation of escapes inside multiline expressions like | |
|
800 | # triple-quoted strings or parenthesized expressions. | |
|
801 | # | |
|
802 | # The last heuristic, while ugly, ensures that the first line of an | |
|
803 | # indented block is correctly transformed. | |
|
804 | # | |
|
805 | # FIXME: try to find a cleaner approach for this last bit. | |
|
806 | ||
|
807 | # Store raw source before applying any transformations to it. Note | |
|
808 | # that this must be done *after* the reset() call that would otherwise | |
|
809 | # flush the buffer. | |
|
810 | self._store(lines, self._buffer_raw, 'source_raw') | |
|
811 | ||
|
812 | push = super(IPythonInputSplitter, self).push | |
|
813 | buf = self._buffer | |
|
814 | for line in lines_list: | |
|
815 | if self._is_complete or not buf or \ | |
|
816 | (buf and buf[-1].rstrip().endswith((':', ','))): | |
|
817 | for f in transforms: | |
|
818 | line = f(line) | |
|
819 | ||
|
820 | out = push(line) | |
|
821 | return out | |
|
822 | ||
|
714 | 823 | def push(self, lines): |
|
715 | 824 | """Push one or more lines of IPython input. |
|
716 | 825 | |
@@ -734,25 +843,19 b' class IPythonInputSplitter(InputSplitter):' | |||
|
734 | 843 | this value is also stored as a private attribute (_is_complete), so it |
|
735 | 844 | can be queried at any time. |
|
736 | 845 | """ |
|
846 | print('mode:', self.input_mode) | |
|
847 | print('lines:',repr(lines)) | |
|
737 | 848 | if not lines: |
|
738 | 849 | return super(IPythonInputSplitter, self).push(lines) |
|
739 | 850 | |
|
740 | 851 | # We must ensure all input is pure unicode |
|
741 | 852 | lines = cast_unicode(lines, self.encoding) |
|
742 | 853 | |
|
743 | # cell magic support | |
|
744 | #print('IM:', self.input_mode,'\n'+lines); print('---') # dbg | |
|
745 | #if self.input_mode == 'cell' and lines.startswith('%%'): | |
|
746 | if lines.startswith('%%'): | |
|
747 | # Cell magics bypass all further transformations | |
|
748 | self.reset() | |
|
749 | self._is_complete = is_complete = True | |
|
750 | first, _, body = lines.partition('\n') | |
|
751 | magic_name, _, line = first.partition(' ') | |
|
752 | magic_name = magic_name.lstrip(ESC_MAGIC) | |
|
753 | self.cell_magic_body = body | |
|
754 | tpl = 'get_ipython()._cell_magic(%r, %r)' | |
|
755 | lines = tpl % (magic_name, line) | |
|
854 | if self.input_mode == 'line': | |
|
855 | return self._push_line_mode(lines) | |
|
856 | ||
|
857 | ## else: | |
|
858 | ## return self._push_cell_mode(lines) | |
|
756 | 859 | |
|
757 | 860 | lines_list = lines.splitlines() |
|
758 | 861 | |
@@ -796,8 +899,7 b' class IPythonInputSplitter(InputSplitter):' | |||
|
796 | 899 | buf = self._buffer |
|
797 | 900 | for line in lines_list: |
|
798 | 901 | if self._is_complete or not buf or \ |
|
799 |
(buf and |
|
|
800 | buf[-1].rstrip().endswith(',')) ): | |
|
902 | (buf and buf[-1].rstrip().endswith((':', ','))): | |
|
801 | 903 | for f in transforms: |
|
802 | 904 | line = f(line) |
|
803 | 905 |
@@ -2027,7 +2027,12 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2027 | 2027 | """ |
|
2028 | 2028 | fn = self.find_line_magic(magic_name) |
|
2029 | 2029 | if fn is None: |
|
2030 |
e |
|
|
2030 | em = "Line magic function `%%%s` not found" % magic_name | |
|
2031 | cm = self.find_cell_magic(magic_name) | |
|
2032 | if cm is not None: | |
|
2033 | em += (' (Did you by chance mean the cell magic `%%%%%s` ' | |
|
2034 | 'instead?).') | |
|
2035 | error() | |
|
2031 | 2036 | else: |
|
2032 | 2037 | # Note: this is the distance in the stack to the user's frame. |
|
2033 | 2038 | # This will need to be updated if the internal calling logic gets |
@@ -2469,13 +2474,9 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2469 | 2474 | self.showtraceback() |
|
2470 | 2475 | warn('Unknown failure executing module: <%s>' % mod_name) |
|
2471 | 2476 | |
|
2472 | def call_cell_magic(self, raw_cell, store_history=False): | |
|
2473 | line, _, cell = raw_cell.partition(os.linesep) | |
|
2474 | magic_name, _, line = line.partition(' ') | |
|
2475 | magic_name = magic_name.lstrip(prefilter.ESC_MAGIC) | |
|
2476 | return self.cell_magic(magic_name, line, cell) | |
|
2477 | ||
|
2478 | 2477 | def _cell_magic(self, magic_name, line): |
|
2478 | """Special method to call a cell magic with the data stored in self. | |
|
2479 | """ | |
|
2479 | 2480 | cell = self._current_cell_magic_body |
|
2480 | 2481 | self._current_cell_magic_body = None |
|
2481 | 2482 | return self.cell_magic(magic_name, line, cell) |
@@ -2507,8 +2508,9 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2507 | 2508 | # ugly, we need to do something cleaner later... Now the logic is |
|
2508 | 2509 | # simply that the input_splitter remembers if there was a cell magic, |
|
2509 | 2510 | # and in that case we grab the cell body. |
|
2510 |
if self.input_splitter.cell_magic_ |
|
|
2511 |
self._current_cell_magic_body = |
|
|
2511 | if self.input_splitter.cell_magic_parts: | |
|
2512 | self._current_cell_magic_body = \ | |
|
2513 | ''.join(self.input_splitter.cell_magic_parts) | |
|
2512 | 2514 | cell = self.input_splitter.source_reset() |
|
2513 | 2515 | |
|
2514 | 2516 | with self.builtin_trap: |
@@ -599,6 +599,41 b' def test_escaped_paren():' | |||
|
599 | 599 | tt.check_pairs(isp.transform_escaped, syntax['escaped_paren']) |
|
600 | 600 | |
|
601 | 601 | |
|
602 | def test_last_blank(): | |
|
603 | nt.assert_false(isp.last_blank('')) | |
|
604 | nt.assert_false(isp.last_blank('abc')) | |
|
605 | nt.assert_false(isp.last_blank('abc\n')) | |
|
606 | nt.assert_false(isp.last_blank('abc\na')) | |
|
607 | nt.assert_true(isp.last_blank('\n')) | |
|
608 | nt.assert_true(isp.last_blank('\n ')) | |
|
609 | nt.assert_true(isp.last_blank('abc\n ')) | |
|
610 | nt.assert_true(isp.last_blank('abc\n\n')) | |
|
611 | ||
|
612 | ||
|
613 | def test_cell_magics(): | |
|
614 | from IPython.core import magic | |
|
615 | ||
|
616 | cell = """\ | |
|
617 | %%cellm line | |
|
618 | body | |
|
619 | """ | |
|
620 | sp = isp.IPythonInputSplitter(input_mode='line') | |
|
621 | sp.push(cell) | |
|
622 | nt.assert_equal(sp.cell_magic_parts, ['body\n']) | |
|
623 | out = sp.source | |
|
624 | ref = u"get_ipython()._cell_magic(u'cellm', u'line')\n" | |
|
625 | nt.assert_equal(out, ref) | |
|
626 | ||
|
627 | sp.reset() | |
|
628 | ||
|
629 | sp.push('%%cellm line2\n') | |
|
630 | nt.assert_true(sp.push_accepts_more()) #1 | |
|
631 | sp.push('\n') | |
|
632 | nt.assert_true(sp.push_accepts_more()) #2 | |
|
633 | sp.push('\n') | |
|
634 | nt.assert_false(sp.push_accepts_more()) #3 | |
|
635 | ||
|
636 | ||
|
602 | 637 | class IPythonInputTestCase(InputSplitterTestCase): |
|
603 | 638 | """By just creating a new class whose .isp is a different instance, we |
|
604 | 639 | re-run the same test battery on the new input splitter. |
General Comments 0
You need to be logged in to leave comments.
Login now