Show More
@@ -70,6 +70,8 b' import ast' | |||||
70 | import codeop |
|
70 | import codeop | |
71 | import re |
|
71 | import re | |
72 | import sys |
|
72 | import sys | |
|
73 | import tokenize | |||
|
74 | from StringIO import StringIO | |||
73 |
|
75 | |||
74 | # IPython modules |
|
76 | # IPython modules | |
75 | from IPython.utils.text import make_quoted_expr |
|
77 | from IPython.utils.text import make_quoted_expr | |
@@ -156,6 +158,24 b' def remove_comments(src):' | |||||
156 |
|
158 | |||
157 | return re.sub('#.*', '', src) |
|
159 | return re.sub('#.*', '', src) | |
158 |
|
160 | |||
|
161 | def has_comment(src): | |||
|
162 | """Indicate whether an input line has (i.e. ends in, or is) a comment. | |||
|
163 | ||||
|
164 | This uses tokenize, so it can distinguish comments from # inside strings. | |||
|
165 | ||||
|
166 | Parameters | |||
|
167 | ---------- | |||
|
168 | src : string | |||
|
169 | A single line input string. | |||
|
170 | ||||
|
171 | Returns | |||
|
172 | ------- | |||
|
173 | Boolean: True if source has a comment. | |||
|
174 | """ | |||
|
175 | readline = StringIO(src).readline | |||
|
176 | toktypes = set(t[0] for t in tokenize.generate_tokens(readline)) | |||
|
177 | return(tokenize.COMMENT in toktypes) | |||
|
178 | ||||
159 |
|
179 | |||
160 | def get_input_encoding(): |
|
180 | def get_input_encoding(): | |
161 | """Return the default standard input encoding. |
|
181 | """Return the default standard input encoding. | |
@@ -173,20 +193,14 b' def get_input_encoding():' | |||||
173 | #----------------------------------------------------------------------------- |
|
193 | #----------------------------------------------------------------------------- | |
174 |
|
194 | |||
175 | class InputSplitter(object): |
|
195 | class InputSplitter(object): | |
176 |
"""An object that can |
|
196 | """An object that can accumulate lines of Python source before execution. | |
177 |
|
||||
178 | This object is designed to be used in one of two basic modes: |
|
|||
179 |
|
197 | |||
180 | 1. By feeding it python source line-by-line, using :meth:`push`. In this |
|
198 | This object is designed to be fed python source line-by-line, using | |
181 |
|
|
199 | :meth:`push`. It will return on each push whether the currently pushed | |
182 |
could be executed already. |
|
200 | code could be executed already. In addition, it provides a method called | |
183 | :meth:`push_accepts_more` that can be used to query whether more input |
|
201 | :meth:`push_accepts_more` that can be used to query whether more input | |
184 | can be pushed into a single interactive block. |
|
202 | can be pushed into a single interactive block. | |
185 |
|
203 | |||
186 | 2. By calling :meth:`split_blocks` with a single, multiline Python string, |
|
|||
187 | that is then split into blocks each of which can be executed |
|
|||
188 | interactively as a single statement. |
|
|||
189 |
|
||||
190 | This is a simple example of how an interactive terminal-based client can use |
|
204 | This is a simple example of how an interactive terminal-based client can use | |
191 | this tool:: |
|
205 | this tool:: | |
192 |
|
206 | |||
@@ -347,9 +361,6 b' class InputSplitter(object):' | |||||
347 | *line-oriented* frontends, since it means that intermediate blank lines |
|
361 | *line-oriented* frontends, since it means that intermediate blank lines | |
348 | are not allowed in function definitions (or any other indented block). |
|
362 | are not allowed in function definitions (or any other indented block). | |
349 |
|
363 | |||
350 | Block-oriented frontends that have a separate keyboard event to |
|
|||
351 | indicate execution should use the :meth:`split_blocks` method instead. |
|
|||
352 |
|
||||
353 | If the current input produces a syntax error, this method immediately |
|
364 | If the current input produces a syntax error, this method immediately | |
354 | returns False but does *not* raise the syntax error exception, as |
|
365 | returns False but does *not* raise the syntax error exception, as | |
355 | typically clients will want to send invalid syntax to an execution |
|
366 | typically clients will want to send invalid syntax to an execution | |
@@ -658,6 +669,42 b' def transform_ipy_prompt(line):' | |||||
658 | return line |
|
669 | return line | |
659 |
|
670 | |||
660 |
|
671 | |||
|
672 | def _make_help_call(target, esc, lspace, next_input=None): | |||
|
673 | """Prepares a pinfo(2)/psearch call from a target name and the escape | |||
|
674 | (i.e. ? or ??)""" | |||
|
675 | method = 'pinfo2' if esc == '??' \ | |||
|
676 | else 'psearch' if '*' in target \ | |||
|
677 | else 'pinfo' | |||
|
678 | ||||
|
679 | if next_input: | |||
|
680 | tpl = '%sget_ipython().magic(u"%s %s", next_input=%s)' | |||
|
681 | return tpl % (lspace, method, target, make_quoted_expr(next_input)) | |||
|
682 | else: | |||
|
683 | return '%sget_ipython().magic(u"%s %s")' % (lspace, method, target) | |||
|
684 | ||||
|
685 | _initial_space_re = re.compile(r'\s*') | |||
|
686 | _help_end_re = re.compile(r"""(%? | |||
|
687 | [a-zA-Z_*][a-zA-Z0-9_*]* # Variable name | |||
|
688 | (\.[a-zA-Z_*][a-zA-Z0-9_*]*)* # .etc.etc | |||
|
689 | ) | |||
|
690 | (\?\??)$ # ? or ??""", | |||
|
691 | re.VERBOSE) | |||
|
692 | def transform_help_end(line): | |||
|
693 | """Translate lines with ?/?? at the end""" | |||
|
694 | m = _help_end_re.search(line) | |||
|
695 | if m is None or has_comment(line): | |||
|
696 | return line | |||
|
697 | target = m.group(1) | |||
|
698 | esc = m.group(3) | |||
|
699 | lspace = _initial_space_re.match(line).group(0) | |||
|
700 | newline = _make_help_call(target, esc, lspace) | |||
|
701 | ||||
|
702 | # If we're mid-command, put it back on the next prompt for the user. | |||
|
703 | next_input = line.rstrip('?') if line.strip() != m.group(0) else None | |||
|
704 | ||||
|
705 | return _make_help_call(target, esc, lspace, next_input) | |||
|
706 | ||||
|
707 | ||||
661 | class EscapedTransformer(object): |
|
708 | class EscapedTransformer(object): | |
662 | """Class to transform lines that are explicitly escaped out.""" |
|
709 | """Class to transform lines that are explicitly escaped out.""" | |
663 |
|
710 | |||
@@ -695,27 +742,7 b' class EscapedTransformer(object):' | |||||
695 | if not line_info.line[1:]: |
|
742 | if not line_info.line[1:]: | |
696 | return 'get_ipython().show_usage()' |
|
743 | return 'get_ipython().show_usage()' | |
697 |
|
744 | |||
698 | # There may be one or two '?' at the end, move them to the front so that |
|
745 | return _make_help_call(line_info.fpart, line_info.esc, line_info.lspace) | |
699 | # the rest of the logic can assume escapes are at the start |
|
|||
700 | l_ori = line_info |
|
|||
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, and |
|
|||
709 | # special-case the psearch syntax |
|
|||
710 | pinfo = 'pinfo' # default |
|
|||
711 | if '*' in line_info.line: |
|
|||
712 | pinfo = 'psearch' |
|
|||
713 | elif line_info.esc == '??': |
|
|||
714 | pinfo = 'pinfo2' |
|
|||
715 |
|
||||
716 | tpl = '%sget_ipython().magic(u"%s %s")' |
|
|||
717 | return tpl % (line_info.lspace, pinfo, |
|
|||
718 | ' '.join([line_info.fpart, line_info.rest]).strip()) |
|
|||
719 |
|
746 | |||
720 | @staticmethod |
|
747 | @staticmethod | |
721 | def _tr_magic(line_info): |
|
748 | def _tr_magic(line_info): | |
@@ -756,12 +783,7 b' class EscapedTransformer(object):' | |||||
756 | # Get line endpoints, where the escapes can be |
|
783 | # Get line endpoints, where the escapes can be | |
757 | line_info = LineInfo(line) |
|
784 | line_info = LineInfo(line) | |
758 |
|
785 | |||
759 | # If the escape is not at the start, only '?' needs to be special-cased. |
|
|||
760 | # All other escapes are only valid at the start |
|
|||
761 | if not line_info.esc in self.tr: |
|
786 | if not line_info.esc in self.tr: | |
762 | if line.endswith(ESC_HELP): |
|
|||
763 | return self._tr_help(line_info) |
|
|||
764 | else: |
|
|||
765 |
|
|
787 | # If we don't recognize the escape, don't modify the line | |
766 |
|
|
788 | return line | |
767 |
|
789 | |||
@@ -815,9 +837,9 b' class IPythonInputSplitter(InputSplitter):' | |||||
815 |
|
837 | |||
816 | lines_list = lines.splitlines() |
|
838 | lines_list = lines.splitlines() | |
817 |
|
839 | |||
818 |
transforms = [transform_ |
|
840 | transforms = [transform_ipy_prompt, transform_classic_prompt, | |
819 |
transform_ |
|
841 | transform_escaped, transform_help_end, | |
820 |
transform_ |
|
842 | transform_assign_system, transform_assign_magic] | |
821 |
|
843 | |||
822 | # Transform logic |
|
844 | # Transform logic | |
823 | # |
|
845 | # |
@@ -1703,7 +1703,8 b' class InteractiveShell(SingletonConfigurable, Magic):' | |||||
1703 | [D:\ipython]|1> _ip.set_next_input("Hello Word") |
|
1703 | [D:\ipython]|1> _ip.set_next_input("Hello Word") | |
1704 | [D:\ipython]|2> Hello Word_ # cursor is here |
|
1704 | [D:\ipython]|2> Hello Word_ # cursor is here | |
1705 | """ |
|
1705 | """ | |
1706 |
|
1706 | if isinstance(s, unicode): | ||
|
1707 | s = s.encode(self.stdin_encoding, 'replace') | |||
1707 | self.rl_next_input = s |
|
1708 | self.rl_next_input = s | |
1708 |
|
1709 | |||
1709 | # Maybe move this to the terminal subclass? |
|
1710 | # Maybe move this to the terminal subclass? | |
@@ -1841,7 +1842,7 b' class InteractiveShell(SingletonConfigurable, Magic):' | |||||
1841 | from . import history |
|
1842 | from . import history | |
1842 | history.init_ipython(self) |
|
1843 | history.init_ipython(self) | |
1843 |
|
1844 | |||
1844 | def magic(self,arg_s): |
|
1845 | def magic(self, arg_s, next_input=None): | |
1845 | """Call a magic function by name. |
|
1846 | """Call a magic function by name. | |
1846 |
|
1847 | |||
1847 | Input: a string containing the name of the magic function to call and |
|
1848 | Input: a string containing the name of the magic function to call and | |
@@ -1858,6 +1859,11 b' class InteractiveShell(SingletonConfigurable, Magic):' | |||||
1858 | valid Python code you can type at the interpreter, including loops and |
|
1859 | valid Python code you can type at the interpreter, including loops and | |
1859 | compound statements. |
|
1860 | compound statements. | |
1860 | """ |
|
1861 | """ | |
|
1862 | # Allow setting the next input - this is used if the user does `a=abs?`. | |||
|
1863 | # We do this first so that magic functions can override it. | |||
|
1864 | if next_input: | |||
|
1865 | self.set_next_input(next_input) | |||
|
1866 | ||||
1861 | args = arg_s.split(' ',1) |
|
1867 | args = arg_s.split(' ',1) | |
1862 | magic_name = args[0] |
|
1868 | magic_name = args[0] | |
1863 | magic_name = magic_name.lstrip(prefilter.ESC_MAGIC) |
|
1869 | magic_name = magic_name.lstrip(prefilter.ESC_MAGIC) |
@@ -25,6 +25,7 b' import nose.tools as nt' | |||||
25 |
|
25 | |||
26 | # Our own |
|
26 | # Our own | |
27 | from IPython.core import inputsplitter as isp |
|
27 | from IPython.core import inputsplitter as isp | |
|
28 | from IPython.testing import tools as tt | |||
28 |
|
29 | |||
29 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
30 | # Semi-complete examples (also used as tests) |
|
31 | # Semi-complete examples (also used as tests) | |
@@ -92,9 +93,7 b' def test_spaces():' | |||||
92 | ('\tx', 1), |
|
93 | ('\tx', 1), | |
93 | ('\t x', 2), |
|
94 | ('\t x', 2), | |
94 | ] |
|
95 | ] | |
95 |
|
96 | tt.check_pairs(isp.num_ini_spaces, tests) | ||
96 | for s, nsp in tests: |
|
|||
97 | nt.assert_equal(isp.num_ini_spaces(s), nsp) |
|
|||
98 |
|
97 | |||
99 |
|
98 | |||
100 | def test_remove_comments(): |
|
99 | def test_remove_comments(): | |
@@ -106,9 +105,19 b' def test_remove_comments():' | |||||
106 | ('line # c \nline#c2 \nline\nline #c\n\n', |
|
105 | ('line # c \nline#c2 \nline\nline #c\n\n', | |
107 | 'line \nline\nline\nline \n\n'), |
|
106 | 'line \nline\nline\nline \n\n'), | |
108 | ] |
|
107 | ] | |
109 |
|
108 | tt.check_pairs(isp.remove_comments, tests) | ||
110 | for inp, out in tests: |
|
109 | ||
111 | nt.assert_equal(isp.remove_comments(inp), out) |
|
110 | def test_has_comment(): | |
|
111 | tests = [('text', False), | |||
|
112 | ('text #comment', True), | |||
|
113 | ('text #comment\n', True), | |||
|
114 | ('#comment', True), | |||
|
115 | ('#comment\n', True), | |||
|
116 | ('a = "#string"', False), | |||
|
117 | ('a = "#string" # comment', True), | |||
|
118 | ('a #comment not "string"', True), | |||
|
119 | ] | |||
|
120 | tt.check_pairs(isp.has_comment, tests) | |||
112 |
|
121 | |||
113 |
|
122 | |||
114 | def test_get_input_encoding(): |
|
123 | def test_get_input_encoding(): | |
@@ -434,11 +443,21 b' syntax = \\' | |||||
434 | [ ('?', 'get_ipython().show_usage()'), |
|
443 | [ ('?', 'get_ipython().show_usage()'), | |
435 | ('?x1', 'get_ipython().magic(u"pinfo x1")'), |
|
444 | ('?x1', 'get_ipython().magic(u"pinfo x1")'), | |
436 | ('??x2', 'get_ipython().magic(u"pinfo2 x2")'), |
|
445 | ('??x2', 'get_ipython().magic(u"pinfo2 x2")'), | |
437 |
(' |
|
446 | ('?a.*s', 'get_ipython().magic(u"psearch a.*s")'), | |
|
447 | ('?%hist', 'get_ipython().magic(u"pinfo %hist")'), | |||
|
448 | ('?abc = qwe', 'get_ipython().magic(u"pinfo abc")'), | |||
|
449 | ], | |||
|
450 | ||||
|
451 | end_help = | |||
|
452 | [ ('x3?', 'get_ipython().magic(u"pinfo x3")'), | |||
438 |
|
|
453 | ('x4??', 'get_ipython().magic(u"pinfo2 x4")'), | |
439 |
|
|
454 | ('%hist?', 'get_ipython().magic(u"pinfo %hist")'), | |
440 |
|
|
455 | ('f*?', 'get_ipython().magic(u"psearch f*")'), | |
441 |
|
|
456 | ('ax.*aspe*?', 'get_ipython().magic(u"psearch ax.*aspe*")'), | |
|
457 | ('a = abc?', 'get_ipython().magic(u"pinfo abc", next_input=u"a = abc")'), | |||
|
458 | ('a = abc.qe??', 'get_ipython().magic(u"pinfo2 abc.qe", next_input=u"a = abc.qe")'), | |||
|
459 | ('a = *.items?', 'get_ipython().magic(u"psearch *.items", next_input=u"a = *.items")'), | |||
|
460 | ('a*2 #comment?', 'a*2 #comment?'), | |||
442 |
|
|
461 | ], | |
443 |
|
462 | |||
444 | # Explicit magic calls |
|
463 | # Explicit magic calls | |
@@ -472,6 +491,14 b' syntax = \\' | |||||
472 | ('/f a b', 'f(a, b)'), |
|
491 | ('/f a b', 'f(a, b)'), | |
473 | ], |
|
492 | ], | |
474 |
|
493 | |||
|
494 | # Check that we transform prompts before other transforms | |||
|
495 | mixed = | |||
|
496 | [ ('In [1]: %lsmagic', 'get_ipython().magic(u"lsmagic")'), | |||
|
497 | ('>>> %lsmagic', 'get_ipython().magic(u"lsmagic")'), | |||
|
498 | ('In [2]: !ls', 'get_ipython().system(u"ls")'), | |||
|
499 | ('In [3]: abs?', 'get_ipython().magic(u"pinfo abs")'), | |||
|
500 | ('In [4]: b = %who', 'b = get_ipython().magic(u"who")'), | |||
|
501 | ], | |||
475 | ) |
|
502 | ) | |
476 |
|
503 | |||
477 | # multiline syntax examples. Each of these should be a list of lists, with |
|
504 | # multiline syntax examples. Each of these should be a list of lists, with | |
@@ -496,11 +523,11 b' syntax_ml = \\' | |||||
496 |
|
523 | |||
497 |
|
524 | |||
498 | def test_assign_system(): |
|
525 | def test_assign_system(): | |
499 | transform_checker(syntax['assign_system'], isp.transform_assign_system) |
|
526 | tt.check_pairs(isp.transform_assign_system, syntax['assign_system']) | |
500 |
|
527 | |||
501 |
|
528 | |||
502 | def test_assign_magic(): |
|
529 | def test_assign_magic(): | |
503 | transform_checker(syntax['assign_magic'], isp.transform_assign_magic) |
|
530 | tt.check_pairs(isp.transform_assign_magic, syntax['assign_magic']) | |
504 |
|
531 | |||
505 |
|
532 | |||
506 | def test_classic_prompt(): |
|
533 | def test_classic_prompt(): | |
@@ -514,33 +541,35 b' def test_ipy_prompt():' | |||||
514 | for example in syntax_ml['ipy_prompt']: |
|
541 | for example in syntax_ml['ipy_prompt']: | |
515 | transform_checker(example, isp.transform_ipy_prompt) |
|
542 | transform_checker(example, isp.transform_ipy_prompt) | |
516 |
|
543 | |||
|
544 | def test_end_help(): | |||
|
545 | tt.check_pairs(isp.transform_help_end, syntax['end_help']) | |||
517 |
|
546 | |||
518 | def test_escaped_noesc(): |
|
547 | def test_escaped_noesc(): | |
519 | transform_checker(syntax['escaped_noesc'], isp.transform_escaped) |
|
548 | tt.check_pairs(isp.transform_escaped, syntax['escaped_noesc']) | |
520 |
|
549 | |||
521 |
|
550 | |||
522 | def test_escaped_shell(): |
|
551 | def test_escaped_shell(): | |
523 | transform_checker(syntax['escaped_shell'], isp.transform_escaped) |
|
552 | tt.check_pairs(isp.transform_escaped, syntax['escaped_shell']) | |
524 |
|
553 | |||
525 |
|
554 | |||
526 | def test_escaped_help(): |
|
555 | def test_escaped_help(): | |
527 | transform_checker(syntax['escaped_help'], isp.transform_escaped) |
|
556 | tt.check_pairs(isp.transform_escaped, syntax['escaped_help']) | |
528 |
|
557 | |||
529 |
|
558 | |||
530 | def test_escaped_magic(): |
|
559 | def test_escaped_magic(): | |
531 | transform_checker(syntax['escaped_magic'], isp.transform_escaped) |
|
560 | tt.check_pairs(isp.transform_escaped, syntax['escaped_magic']) | |
532 |
|
561 | |||
533 |
|
562 | |||
534 | def test_escaped_quote(): |
|
563 | def test_escaped_quote(): | |
535 | transform_checker(syntax['escaped_quote'], isp.transform_escaped) |
|
564 | tt.check_pairs(isp.transform_escaped, syntax['escaped_quote']) | |
536 |
|
565 | |||
537 |
|
566 | |||
538 | def test_escaped_quote2(): |
|
567 | def test_escaped_quote2(): | |
539 | transform_checker(syntax['escaped_quote2'], isp.transform_escaped) |
|
568 | tt.check_pairs(isp.transform_escaped, syntax['escaped_quote2']) | |
540 |
|
569 | |||
541 |
|
570 | |||
542 | def test_escaped_paren(): |
|
571 | def test_escaped_paren(): | |
543 | transform_checker(syntax['escaped_paren'], isp.transform_escaped) |
|
572 | tt.check_pairs(isp.transform_escaped, syntax['escaped_paren']) | |
544 |
|
573 | |||
545 |
|
574 | |||
546 | class IPythonInputTestCase(InputSplitterTestCase): |
|
575 | class IPythonInputTestCase(InputSplitterTestCase): |
@@ -281,3 +281,29 b' class TempFileMixin(object):' | |||||
281 | # delete it. I have no clue why |
|
281 | # delete it. I have no clue why | |
282 | pass |
|
282 | pass | |
283 |
|
283 | |||
|
284 | pair_fail_msg = ("Testing function {0}\n\n" | |||
|
285 | "In:\n" | |||
|
286 | " {1!r}\n" | |||
|
287 | "Expected:\n" | |||
|
288 | " {2!r}\n" | |||
|
289 | "Got:\n" | |||
|
290 | " {3!r}\n") | |||
|
291 | def check_pairs(func, pairs): | |||
|
292 | """Utility function for the common case of checking a function with a | |||
|
293 | sequence of input/output pairs. | |||
|
294 | ||||
|
295 | Parameters | |||
|
296 | ---------- | |||
|
297 | func : callable | |||
|
298 | The function to be tested. Should accept a single argument. | |||
|
299 | pairs : iterable | |||
|
300 | A list of (input, expected_output) tuples. | |||
|
301 | ||||
|
302 | Returns | |||
|
303 | ------- | |||
|
304 | None. Raises an AssertionError if any output does not match the expected | |||
|
305 | value. | |||
|
306 | """ | |||
|
307 | for inp, expected in pairs: | |||
|
308 | out = func(inp) | |||
|
309 | assert out == expected, pair_fail_msg.format(func.func_name, inp, expected, out) |
@@ -33,7 +33,7 b' ZMQ architecture' | |||||
33 | There is a new GUI framework for IPython, based on a client-server model in |
|
33 | There is a new GUI framework for IPython, based on a client-server model in | |
34 | which multiple clients can communicate with one IPython kernel, using the |
|
34 | which multiple clients can communicate with one IPython kernel, using the | |
35 | ZeroMQ messaging framework. There is already a Qt console client, which can |
|
35 | ZeroMQ messaging framework. There is already a Qt console client, which can | |
36 |
be started by calling ``ipython |
|
36 | be started by calling ``ipython qtconsole``. The protocol is :ref:`documented | |
37 | <messaging>`. |
|
37 | <messaging>`. | |
38 |
|
38 | |||
39 | The parallel computing framework has also been rewritten using ZMQ. The |
|
39 | The parallel computing framework has also been rewritten using ZMQ. The | |
@@ -43,6 +43,10 b' new :mod:`IPython.parallel` module.' | |||||
43 | New features |
|
43 | New features | |
44 | ------------ |
|
44 | ------------ | |
45 |
|
45 | |||
|
46 | * You can now get help on an object halfway through typing a command. For | |||
|
47 | instance, typing ``a = zip?`` shows the details of :func:`zip`. It also | |||
|
48 | leaves the command at the next prompt so you can carry on with it. | |||
|
49 | ||||
46 | * The input history is now written to an SQLite database. The API for |
|
50 | * The input history is now written to an SQLite database. The API for | |
47 | retrieving items from the history has also been redesigned. |
|
51 | retrieving items from the history has also been redesigned. | |
48 |
|
52 | |||
@@ -63,7 +67,7 b' New features' | |||||
63 |
|
67 | |||
64 | * The methods of :class:`~IPython.core.iplib.InteractiveShell` have |
|
68 | * The methods of :class:`~IPython.core.iplib.InteractiveShell` have | |
65 | been organized into sections to make it easier to turn more sections |
|
69 | been organized into sections to make it easier to turn more sections | |
66 |
of functionality into componen |
|
70 | of functionality into components. | |
67 |
|
71 | |||
68 | * The embedded shell has been refactored into a truly standalone subclass of |
|
72 | * The embedded shell has been refactored into a truly standalone subclass of | |
69 | :class:`InteractiveShell` called :class:`InteractiveShellEmbed`. All |
|
73 | :class:`InteractiveShell` called :class:`InteractiveShellEmbed`. All |
General Comments 0
You need to be logged in to leave comments.
Login now