##// END OF EJS Templates
Change get_source() api as per code review, to source_reset()....
Fernando Perez -
Show More
@@ -1,266 +1,260 b''
1 """Analysis of text input into executable blocks.
1 """Analysis of text input into executable blocks.
2
2
3 This is a simple example of how an interactive terminal-based client can use
3 This is a simple example of how an interactive terminal-based client can use
4 this tool::
4 this tool::
5
5
6 bb = BlockBreaker()
6 bb = BlockBreaker()
7 while not bb.interactive_block_ready():
7 while not bb.interactive_block_ready():
8 bb.push(raw_input('>>> '))
8 bb.push(raw_input('>>> '))
9 print 'Input source was:\n', bb.source,
9 print 'Input source was:\n', bb.source,
10 """
10 """
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2010 The IPython Development Team
12 # Copyright (C) 2010 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # stdlib
21 # stdlib
22 import codeop
22 import codeop
23 import re
23 import re
24 import sys
24 import sys
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Utilities
27 # Utilities
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # FIXME: move these utilities to the general ward...
30 # FIXME: move these utilities to the general ward...
31
31
32 # compiled regexps for autoindent management
32 # compiled regexps for autoindent management
33 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
33 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
34 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
34 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
35
35
36
36
37 def num_ini_spaces(s):
37 def num_ini_spaces(s):
38 """Return the number of initial spaces in a string.
38 """Return the number of initial spaces in a string.
39
39
40 Note that tabs are counted as a single space. For now, we do *not* support
40 Note that tabs are counted as a single space. For now, we do *not* support
41 mixing of tabs and spaces in the user's input.
41 mixing of tabs and spaces in the user's input.
42
42
43 Parameters
43 Parameters
44 ----------
44 ----------
45 s : string
45 s : string
46 """
46 """
47
47
48 ini_spaces = ini_spaces_re.match(s)
48 ini_spaces = ini_spaces_re.match(s)
49 if ini_spaces:
49 if ini_spaces:
50 return ini_spaces.end()
50 return ini_spaces.end()
51 else:
51 else:
52 return 0
52 return 0
53
53
54
54
55 def remove_comments(src):
55 def remove_comments(src):
56 """Remove all comments from input source.
56 """Remove all comments from input source.
57
57
58 Note: comments are NOT recognized inside of strings!
58 Note: comments are NOT recognized inside of strings!
59
59
60 Parameters
60 Parameters
61 ----------
61 ----------
62 src : string
62 src : string
63 A single or multiline input string.
63 A single or multiline input string.
64
64
65 Returns
65 Returns
66 -------
66 -------
67 String with all Python comments removed.
67 String with all Python comments removed.
68 """
68 """
69
69
70 return re.sub('#.*', '', src)
70 return re.sub('#.*', '', src)
71
71
72
72
73 def get_input_encoding():
73 def get_input_encoding():
74 """Return the default standard input encoding."""
74 """Return the default standard input encoding."""
75 return getattr(sys.stdin, 'encoding', 'ascii')
75 return getattr(sys.stdin, 'encoding', 'ascii')
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Classes and functions
78 # Classes and functions
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81 class BlockBreaker(object):
81 class BlockBreaker(object):
82 # Command compiler
82 # Command compiler
83 compile = None
83 compile = None
84 # Number of spaces of indentation
84 # Number of spaces of indentation
85 indent_spaces = 0
85 indent_spaces = 0
86 # String, indicating the default input encoding
86 # String, indicating the default input encoding
87 encoding = ''
87 encoding = ''
88 # String where the current full source input is stored, properly encoded
88 # String where the current full source input is stored, properly encoded
89 source = ''
89 source = ''
90 # Code object corresponding to the current source
90 # Code object corresponding to the current source
91 code = None
91 code = None
92 # Boolean indicating whether the current block is complete
92 # Boolean indicating whether the current block is complete
93 is_complete = None
93 is_complete = None
94 # Input mode
94 # Input mode
95 input_mode = 'append'
95 input_mode = 'append'
96
96
97 # Private attributes
97 # Private attributes
98
98
99 # List
99 # List
100 _buffer = None
100 _buffer = None
101
101
102 def __init__(self, input_mode=None):
102 def __init__(self, input_mode=None):
103 """Create a new BlockBreaker instance.
103 """Create a new BlockBreaker instance.
104
104
105 Parameters
105 Parameters
106 ----------
106 ----------
107 input_mode : str
107 input_mode : str
108
108
109 One of 'append', 'replace', default is 'append'. This controls how
109 One of 'append', 'replace', default is 'append'. This controls how
110 new inputs are used: in 'append' mode, they are appended to the
110 new inputs are used: in 'append' mode, they are appended to the
111 existing buffer and the whole buffer is compiled; in 'replace' mode,
111 existing buffer and the whole buffer is compiled; in 'replace' mode,
112 each new input completely replaces all prior inputs. Replace mode is
112 each new input completely replaces all prior inputs. Replace mode is
113 thus equivalent to prepending a full reset() to every push() call.
113 thus equivalent to prepending a full reset() to every push() call.
114
114
115 In practice, line-oriented clients likely want to use 'append' mode
115 In practice, line-oriented clients likely want to use 'append' mode
116 while block-oriented ones will want to use 'replace'.
116 while block-oriented ones will want to use 'replace'.
117 """
117 """
118 self._buffer = []
118 self._buffer = []
119 self.compile = codeop.CommandCompiler()
119 self.compile = codeop.CommandCompiler()
120 self.encoding = get_input_encoding()
120 self.encoding = get_input_encoding()
121 self.input_mode = BlockBreaker.input_mode if input_mode is None \
121 self.input_mode = BlockBreaker.input_mode if input_mode is None \
122 else input_mode
122 else input_mode
123
123
124 def reset(self):
124 def reset(self):
125 """Reset the input buffer and associated state."""
125 """Reset the input buffer and associated state."""
126 self.indent_spaces = 0
126 self.indent_spaces = 0
127 self._buffer[:] = []
127 self._buffer[:] = []
128 self.source = ''
128 self.source = ''
129 self.code = None
129 self.code = None
130
130
131 def get_source(self, reset=False):
131 def source_reset(self):
132 """Return the input source.
132 """Return the input source and perform a full reset.
133
134 Parameters
135 ----------
136 reset : boolean
137 If true, all state is reset and prior input forgotten.
138 """
133 """
139 out = self.source
134 out = self.source
140 if reset:
135 self.reset()
141 self.reset()
142 return out
136 return out
143
137
144 def push(self, lines):
138 def push(self, lines):
145 """Push one ore more lines of input.
139 """Push one ore more lines of input.
146
140
147 This stores the given lines and returns a status code indicating
141 This stores the given lines and returns a status code indicating
148 whether the code forms a complete Python block or not.
142 whether the code forms a complete Python block or not.
149
143
150 Any exceptions generated in compilation are allowed to propagate.
144 Any exceptions generated in compilation are allowed to propagate.
151
145
152 Parameters
146 Parameters
153 ----------
147 ----------
154 lines : string
148 lines : string
155 One or more lines of Python input.
149 One or more lines of Python input.
156
150
157 Returns
151 Returns
158 -------
152 -------
159 is_complete : boolean
153 is_complete : boolean
160 True if the current input source (the result of the current input
154 True if the current input source (the result of the current input
161 plus prior inputs) forms a complete Python execution block. Note that
155 plus prior inputs) forms a complete Python execution block. Note that
162 this value is also stored as an attribute so it can be queried at any
156 this value is also stored as an attribute so it can be queried at any
163 time.
157 time.
164 """
158 """
165 if self.input_mode == 'replace':
159 if self.input_mode == 'replace':
166 self.reset()
160 self.reset()
167
161
168 # If the source code has leading blanks, add 'if 1:\n' to it
162 # If the source code has leading blanks, add 'if 1:\n' to it
169 # this allows execution of indented pasted code. It is tempting
163 # this allows execution of indented pasted code. It is tempting
170 # to add '\n' at the end of source to run commands like ' a=1'
164 # to add '\n' at the end of source to run commands like ' a=1'
171 # directly, but this fails for more complicated scenarios
165 # directly, but this fails for more complicated scenarios
172 if not self._buffer and lines[:1] in [' ', '\t']:
166 if not self._buffer and lines[:1] in [' ', '\t']:
173 lines = 'if 1:\n%s' % lines
167 lines = 'if 1:\n%s' % lines
174
168
175 self._store(lines)
169 self._store(lines)
176 source = self.source
170 source = self.source
177
171
178 # Before calling compile(), reset the code object to None so that if an
172 # Before calling compile(), reset the code object to None so that if an
179 # exception is raised in compilation, we don't mislead by having
173 # exception is raised in compilation, we don't mislead by having
180 # inconsistent code/source attributes.
174 # inconsistent code/source attributes.
181 self.code, self.is_complete = None, None
175 self.code, self.is_complete = None, None
182 try:
176 try:
183 self.code = self.compile(source)
177 self.code = self.compile(source)
184 # Invalid syntax can produce any of a number of different errors from
178 # Invalid syntax can produce any of a number of different errors from
185 # inside the compiler, so we have to catch them all. Syntax errors
179 # inside the compiler, so we have to catch them all. Syntax errors
186 # immediately produce a 'ready' block, so the invalid Python can be
180 # immediately produce a 'ready' block, so the invalid Python can be
187 # sent to the kernel for evaluation with possible ipython
181 # sent to the kernel for evaluation with possible ipython
188 # special-syntax conversion.
182 # special-syntax conversion.
189 except (SyntaxError, OverflowError, ValueError, TypeError, MemoryError):
183 except (SyntaxError, OverflowError, ValueError, TypeError, MemoryError):
190 self.is_complete = True
184 self.is_complete = True
191 else:
185 else:
192 # Compilation didn't produce any exceptions (though it may not have
186 # Compilation didn't produce any exceptions (though it may not have
193 # given a complete code object)
187 # given a complete code object)
194 self.is_complete = self.code is not None
188 self.is_complete = self.code is not None
195 self._update_indent(lines)
189 self._update_indent(lines)
196
190
197 return self.is_complete
191 return self.is_complete
198
192
199 def interactive_block_ready(self):
193 def interactive_block_ready(self):
200 """Return whether a block of interactive input is ready for execution.
194 """Return whether a block of interactive input is ready for execution.
201
195
202 This method is meant to be used by line-oriented frontends, who need to
196 This method is meant to be used by line-oriented frontends, who need to
203 guess whether a block is complete or not based solely on prior and
197 guess whether a block is complete or not based solely on prior and
204 current input lines. The BlockBreaker considers it has a complete
198 current input lines. The BlockBreaker considers it has a complete
205 interactive block when *all* of the following are true:
199 interactive block when *all* of the following are true:
206
200
207 1. The input compiles to a complete statement.
201 1. The input compiles to a complete statement.
208
202
209 2. The indentation level is flush-left (because if we are indented,
203 2. The indentation level is flush-left (because if we are indented,
210 like inside a function definition or for loop, we need to keep
204 like inside a function definition or for loop, we need to keep
211 reading new input).
205 reading new input).
212
206
213 3. There is one extra line consisting only of whitespace.
207 3. There is one extra line consisting only of whitespace.
214
208
215 Because of condition #3, this method should be used only by
209 Because of condition #3, this method should be used only by
216 *line-oriented* frontends, since it means that intermediate blank lines
210 *line-oriented* frontends, since it means that intermediate blank lines
217 are not allowed in function definitions (or any other indented block).
211 are not allowed in function definitions (or any other indented block).
218
212
219 Block-oriented frontends that have a separate keyboard event to
213 Block-oriented frontends that have a separate keyboard event to
220 indicate execution should use the :meth:`split_blocks` method instead.
214 indicate execution should use the :meth:`split_blocks` method instead.
221 """
215 """
222 if not self.is_complete:
216 if not self.is_complete:
223 return False
217 return False
224 if self.indent_spaces==0:
218 if self.indent_spaces==0:
225 return True
219 return True
226 last_line = self.source.splitlines()[-1]
220 last_line = self.source.splitlines()[-1]
227 if not last_line or last_line.isspace():
221 if not last_line or last_line.isspace():
228 return True
222 return True
229 else:
223 else:
230 return False
224 return False
231
225
232 def split_blocks(self, lines):
226 def split_blocks(self, lines):
233 """Split a multiline string into multiple input blocks"""
227 """Split a multiline string into multiple input blocks"""
234 raise NotImplementedError
228 raise NotImplementedError
235
229
236 #------------------------------------------------------------------------
230 #------------------------------------------------------------------------
237 # Private interface
231 # Private interface
238 #------------------------------------------------------------------------
232 #------------------------------------------------------------------------
239
233
240 def _update_indent(self, lines):
234 def _update_indent(self, lines):
241 """Keep track of the indent level."""
235 """Keep track of the indent level."""
242
236
243 for line in remove_comments(lines).splitlines():
237 for line in remove_comments(lines).splitlines():
244
238
245 if line and not line.isspace():
239 if line and not line.isspace():
246 if self.code is not None:
240 if self.code is not None:
247 inisp = num_ini_spaces(line)
241 inisp = num_ini_spaces(line)
248 if inisp < self.indent_spaces:
242 if inisp < self.indent_spaces:
249 self.indent_spaces = inisp
243 self.indent_spaces = inisp
250
244
251 if line[-1] == ':':
245 if line[-1] == ':':
252 self.indent_spaces += 4
246 self.indent_spaces += 4
253 elif dedent_re.match(line):
247 elif dedent_re.match(line):
254 self.indent_spaces -= 4
248 self.indent_spaces -= 4
255
249
256 def _store(self, lines):
250 def _store(self, lines):
257 """Store one or more lines of input.
251 """Store one or more lines of input.
258
252
259 If input lines are not newline-terminated, a newline is automatically
253 If input lines are not newline-terminated, a newline is automatically
260 appended."""
254 appended."""
261
255
262 if lines.endswith('\n'):
256 if lines.endswith('\n'):
263 self._buffer.append(lines)
257 self._buffer.append(lines)
264 else:
258 else:
265 self._buffer.append(lines+'\n')
259 self._buffer.append(lines+'\n')
266 self.source = ''.join(self._buffer).encode(self.encoding)
260 self.source = ''.join(self._buffer).encode(self.encoding)
@@ -1,191 +1,189 b''
1 """Tests for the blockbreaker module.
1 """Tests for the blockbreaker module.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010 The IPython Development Team
4 # Copyright (C) 2010 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # stdlib
13 # stdlib
14 import unittest
14 import unittest
15
15
16 # Third party
16 # Third party
17 import nose.tools as nt
17 import nose.tools as nt
18
18
19 # Our own
19 # Our own
20 from IPython.core import blockbreaker as BB
20 from IPython.core import blockbreaker as BB
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Tests
23 # Tests
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 def test_spaces():
25 def test_spaces():
26 tests = [('', 0),
26 tests = [('', 0),
27 (' ', 1),
27 (' ', 1),
28 ('\n', 0),
28 ('\n', 0),
29 (' \n', 1),
29 (' \n', 1),
30 ('x', 0),
30 ('x', 0),
31 (' x', 1),
31 (' x', 1),
32 (' x',2),
32 (' x',2),
33 (' x',4),
33 (' x',4),
34 # Note: tabs are counted as a single whitespace!
34 # Note: tabs are counted as a single whitespace!
35 ('\tx', 1),
35 ('\tx', 1),
36 ('\t x', 2),
36 ('\t x', 2),
37 ]
37 ]
38
38
39 for s, nsp in tests:
39 for s, nsp in tests:
40 nt.assert_equal(BB.num_ini_spaces(s), nsp)
40 nt.assert_equal(BB.num_ini_spaces(s), nsp)
41
41
42
42
43 def test_remove_comments():
43 def test_remove_comments():
44 tests = [('text', 'text'),
44 tests = [('text', 'text'),
45 ('text # comment', 'text '),
45 ('text # comment', 'text '),
46 ('text # comment\n', 'text \n'),
46 ('text # comment\n', 'text \n'),
47 ('text # comment \n', 'text \n'),
47 ('text # comment \n', 'text \n'),
48 ('line # c \nline\n','line \nline\n'),
48 ('line # c \nline\n','line \nline\n'),
49 ('line # c \nline#c2 \nline\nline #c\n\n',
49 ('line # c \nline#c2 \nline\nline #c\n\n',
50 'line \nline\nline\nline \n\n'),
50 'line \nline\nline\nline \n\n'),
51 ]
51 ]
52
52
53 for inp, out in tests:
53 for inp, out in tests:
54 nt.assert_equal(BB.remove_comments(inp), out)
54 nt.assert_equal(BB.remove_comments(inp), out)
55
55
56
56
57 def test_get_input_encoding():
57 def test_get_input_encoding():
58 encoding = BB.get_input_encoding()
58 encoding = BB.get_input_encoding()
59 nt.assert_true(isinstance(encoding, basestring))
59 nt.assert_true(isinstance(encoding, basestring))
60 # simple-minded check that at least encoding a simple string works with the
60 # simple-minded check that at least encoding a simple string works with the
61 # encoding we got.
61 # encoding we got.
62 nt.assert_equal('test'.encode(encoding), 'test')
62 nt.assert_equal('test'.encode(encoding), 'test')
63
63
64
64
65 class BlockBreakerTestCase(unittest.TestCase):
65 class BlockBreakerTestCase(unittest.TestCase):
66 def setUp(self):
66 def setUp(self):
67 self.bb = BB.BlockBreaker()
67 self.bb = BB.BlockBreaker()
68
68
69 def test_reset(self):
69 def test_reset(self):
70 bb = self.bb
70 bb = self.bb
71 bb.push('x=1')
71 bb.push('x=1')
72 bb.reset()
72 bb.reset()
73 self.assertEqual(bb._buffer, [])
73 self.assertEqual(bb._buffer, [])
74 self.assertEqual(bb.indent_spaces, 0)
74 self.assertEqual(bb.indent_spaces, 0)
75 self.assertEqual(bb.get_source(), '')
75 self.assertEqual(bb.source, '')
76 self.assertEqual(bb.code, None)
76 self.assertEqual(bb.code, None)
77
77
78 def test_source(self):
78 def test_source(self):
79 self.bb._store('1')
79 self.bb._store('1')
80 self.bb._store('2')
80 self.bb._store('2')
81 out = self.bb.get_source()
81 self.assertEqual(self.bb.source, '1\n2\n')
82 self.assertEqual(out, '1\n2\n')
82 self.assertTrue(len(self.bb._buffer)>0)
83 out = self.bb.get_source(reset=True)
83 self.assertEqual(self.bb.source_reset(), '1\n2\n')
84 self.assertEqual(out, '1\n2\n')
85 self.assertEqual(self.bb._buffer, [])
84 self.assertEqual(self.bb._buffer, [])
86 out = self.bb.get_source()
85 self.assertEqual(self.bb.source, '')
87 self.assertEqual(out, '')
88
86
89 def test_indent(self):
87 def test_indent(self):
90 bb = self.bb # shorthand
88 bb = self.bb # shorthand
91 bb.push('x=1')
89 bb.push('x=1')
92 self.assertEqual(bb.indent_spaces, 0)
90 self.assertEqual(bb.indent_spaces, 0)
93 bb.push('if 1:\n x=1')
91 bb.push('if 1:\n x=1')
94 self.assertEqual(bb.indent_spaces, 4)
92 self.assertEqual(bb.indent_spaces, 4)
95 bb.push('y=2\n')
93 bb.push('y=2\n')
96 self.assertEqual(bb.indent_spaces, 0)
94 self.assertEqual(bb.indent_spaces, 0)
97 bb.push('if 1:')
95 bb.push('if 1:')
98 self.assertEqual(bb.indent_spaces, 4)
96 self.assertEqual(bb.indent_spaces, 4)
99 bb.push(' x=1')
97 bb.push(' x=1')
100 self.assertEqual(bb.indent_spaces, 4)
98 self.assertEqual(bb.indent_spaces, 4)
101 # Blank lines shouldn't change the indent level
99 # Blank lines shouldn't change the indent level
102 bb.push(' '*2)
100 bb.push(' '*2)
103 self.assertEqual(bb.indent_spaces, 4)
101 self.assertEqual(bb.indent_spaces, 4)
104
102
105 def test_indent2(self):
103 def test_indent2(self):
106 bb = self.bb
104 bb = self.bb
107 # When a multiline statement contains parens or multiline strings, we
105 # When a multiline statement contains parens or multiline strings, we
108 # shouldn't get confused.
106 # shouldn't get confused.
109 bb.push("if 1:")
107 bb.push("if 1:")
110 bb.push(" x = (1+\n 2)")
108 bb.push(" x = (1+\n 2)")
111 self.assertEqual(bb.indent_spaces, 4)
109 self.assertEqual(bb.indent_spaces, 4)
112
110
113 def test_dedent(self):
111 def test_dedent(self):
114 bb = self.bb # shorthand
112 bb = self.bb # shorthand
115 bb.push('if 1:')
113 bb.push('if 1:')
116 self.assertEqual(bb.indent_spaces, 4)
114 self.assertEqual(bb.indent_spaces, 4)
117 bb.push(' pass')
115 bb.push(' pass')
118 self.assertEqual(bb.indent_spaces, 0)
116 self.assertEqual(bb.indent_spaces, 0)
119
117
120 def test_push(self):
118 def test_push(self):
121 bb = self.bb
119 bb = self.bb
122 bb.push('x=1')
120 bb.push('x=1')
123 self.assertTrue(bb.is_complete)
121 self.assertTrue(bb.is_complete)
124
122
125 def test_push2(self):
123 def test_push2(self):
126 bb = self.bb
124 bb = self.bb
127 bb.push('if 1:')
125 bb.push('if 1:')
128 self.assertFalse(bb.is_complete)
126 self.assertFalse(bb.is_complete)
129 for line in [' x=1', '# a comment', ' y=2']:
127 for line in [' x=1', '# a comment', ' y=2']:
130 bb.push(line)
128 bb.push(line)
131 self.assertTrue(bb.is_complete)
129 self.assertTrue(bb.is_complete)
132
130
133 def test_push3(self):
131 def test_push3(self):
134 """Test input with leading whitespace"""
132 """Test input with leading whitespace"""
135 bb = self.bb
133 bb = self.bb
136 bb.push(' x=1')
134 bb.push(' x=1')
137 bb.push(' y=2')
135 bb.push(' y=2')
138 self.assertEqual(bb.source, 'if 1:\n x=1\n y=2\n')
136 self.assertEqual(bb.source, 'if 1:\n x=1\n y=2\n')
139
137
140 def test_replace_mode(self):
138 def test_replace_mode(self):
141 bb = self.bb
139 bb = self.bb
142 bb.input_mode = 'replace'
140 bb.input_mode = 'replace'
143 bb.push('x=1')
141 bb.push('x=1')
144 self.assertEqual(bb.source, 'x=1\n')
142 self.assertEqual(bb.source, 'x=1\n')
145 bb.push('x=2')
143 bb.push('x=2')
146 self.assertEqual(bb.source, 'x=2\n')
144 self.assertEqual(bb.source, 'x=2\n')
147
145
148 def test_interactive_block_ready(self):
146 def test_interactive_block_ready(self):
149 bb = self.bb
147 bb = self.bb
150 bb.push('x=1')
148 bb.push('x=1')
151 self.assertTrue(bb.interactive_block_ready())
149 self.assertTrue(bb.interactive_block_ready())
152
150
153 def test_interactive_block_ready2(self):
151 def test_interactive_block_ready2(self):
154 bb = self.bb
152 bb = self.bb
155 bb.push('if 1:')
153 bb.push('if 1:')
156 self.assertFalse(bb.interactive_block_ready())
154 self.assertFalse(bb.interactive_block_ready())
157 bb.push(' x=1')
155 bb.push(' x=1')
158 self.assertFalse(bb.interactive_block_ready())
156 self.assertFalse(bb.interactive_block_ready())
159 bb.push('')
157 bb.push('')
160 self.assertTrue(bb.interactive_block_ready())
158 self.assertTrue(bb.interactive_block_ready())
161
159
162 def test_interactive_block_ready3(self):
160 def test_interactive_block_ready3(self):
163 bb = self.bb
161 bb = self.bb
164 bb.push("x = (2+\n3)")
162 bb.push("x = (2+\n3)")
165 self.assertTrue(bb.interactive_block_ready())
163 self.assertTrue(bb.interactive_block_ready())
166
164
167 def test_interactive_block_ready4(self):
165 def test_interactive_block_ready4(self):
168 bb = self.bb
166 bb = self.bb
169 # When a multiline statement contains parens or multiline strings, we
167 # When a multiline statement contains parens or multiline strings, we
170 # shouldn't get confused.
168 # shouldn't get confused.
171 # FIXME: we should be able to better handle de-dents in statements like
169 # FIXME: we should be able to better handle de-dents in statements like
172 # multiline strings and multiline expressions (continued with \ or
170 # multiline strings and multiline expressions (continued with \ or
173 # parens). Right now we aren't handling the indentation tracking quite
171 # parens). Right now we aren't handling the indentation tracking quite
174 # correctly with this, though in practice it may not be too much of a
172 # correctly with this, though in practice it may not be too much of a
175 # problem. We'll need to see.
173 # problem. We'll need to see.
176 bb.push("if 1:")
174 bb.push("if 1:")
177 bb.push(" x = (2+")
175 bb.push(" x = (2+")
178 bb.push(" 3)")
176 bb.push(" 3)")
179 self.assertFalse(bb.interactive_block_ready())
177 self.assertFalse(bb.interactive_block_ready())
180 bb.push(" y = 3")
178 bb.push(" y = 3")
181 self.assertFalse(bb.interactive_block_ready())
179 self.assertFalse(bb.interactive_block_ready())
182 bb.push('')
180 bb.push('')
183 self.assertTrue(bb.interactive_block_ready())
181 self.assertTrue(bb.interactive_block_ready())
184
182
185 def test_syntax_error(self):
183 def test_syntax_error(self):
186 bb = self.bb
184 bb = self.bb
187 # Syntax errors immediately produce a 'ready' block, so the invalid
185 # Syntax errors immediately produce a 'ready' block, so the invalid
188 # Python can be sent to the kernel for evaluation with possible ipython
186 # Python can be sent to the kernel for evaluation with possible ipython
189 # special-syntax conversion.
187 # special-syntax conversion.
190 bb.push('run foo')
188 bb.push('run foo')
191 self.assertTrue(bb.interactive_block_ready())
189 self.assertTrue(bb.interactive_block_ready())
General Comments 0
You need to be logged in to leave comments. Login now