##// END OF EJS Templates
Test completion.
gvaroquaux -
Show More
@@ -1,277 +1,282 b''
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import IPython
22 22 import sys
23 23
24 24 from frontendbase import FrontEndBase
25 25 from IPython.kernel.core.interpreter import Interpreter
26 26
27 27 def common_prefix(strings):
28 28 """ Given a list of strings, return the common prefix between all
29 29 these strings.
30 30 """
31 31 ref = strings[0]
32 32 prefix = ''
33 33 for size in range(len(ref)):
34 34 test_prefix = ref[:size+1]
35 35 for string in strings[1:]:
36 36 if not string.startswith(test_prefix):
37 37 return prefix
38 38 prefix = test_prefix
39 39
40 40 return prefix
41 41
42 42 #-------------------------------------------------------------------------------
43 43 # Base class for the line-oriented front ends
44 44 #-------------------------------------------------------------------------------
45 45 class LineFrontEndBase(FrontEndBase):
46 46 """ Concrete implementation of the FrontEndBase class. This is meant
47 47 to be the base class behind all the frontend that are line-oriented,
48 48 rather than block-oriented.
49 49 """
50 50
51 51 # We need to keep the prompt number, to be able to increment
52 52 # it when there is an exception.
53 53 prompt_number = 1
54 54
55 55 # We keep a reference to the last result: it helps testing and
56 56 # programatic control of the frontend.
57 57 last_result = dict(number=0)
58 58
59 59 # The input buffer being edited
60 60 input_buffer = ''
61 61
62 62 # Set to true for debug output
63 63 debug = False
64 64
65 65 #--------------------------------------------------------------------------
66 66 # FrontEndBase interface
67 67 #--------------------------------------------------------------------------
68 68
69 69 def __init__(self, shell=None, history=None):
70 70 if shell is None:
71 71 shell = Interpreter()
72 72 FrontEndBase.__init__(self, shell=shell, history=history)
73 73
74 74 self.new_prompt(self.input_prompt_template.substitute(number=1))
75 75
76 76
77 77 def complete(self, line):
78 78 """Complete line in engine's user_ns
79 79
80 80 Parameters
81 81 ----------
82 82 line : string
83 83
84 84 Result
85 85 ------
86 86 The replacement for the line and the list of possible completions.
87 87 """
88 88 completions = self.shell.complete(line)
89 89 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
90 90 if completions:
91 91 prefix = common_prefix(completions)
92 92 residual = complete_sep.split(line)[:-1]
93 93 line = line[:-len(residual)] + prefix
94 94 return line, completions
95 95
96 96
97 97 def render_result(self, result):
98 98 """ Frontend-specific rendering of the result of a calculation
99 99 that has been sent to an engine.
100 100 """
101 101 if 'stdout' in result and result['stdout']:
102 102 self.write('\n' + result['stdout'])
103 103 if 'display' in result and result['display']:
104 104 self.write("%s%s\n" % (
105 105 self.output_prompt_template.substitute(
106 106 number=result['number']),
107 107 result['display']['pprint']
108 108 ) )
109 109
110 110
111 111 def render_error(self, failure):
112 112 """ Frontend-specific rendering of error.
113 113 """
114 114 self.write('\n\n'+str(failure)+'\n\n')
115 115 return failure
116 116
117 117
118 118 def is_complete(self, string):
119 119 """ Check if a string forms a complete, executable set of
120 120 commands.
121 121
122 122 For the line-oriented frontend, multi-line code is not executed
123 123 as soon as it is complete: the users has to enter two line
124 124 returns.
125 125 """
126 126 if string in ('', '\n'):
127 127 # Prefiltering, eg through ipython0, may return an empty
128 128 # string although some operations have been accomplished. We
129 129 # thus want to consider an empty string as a complete
130 130 # statement.
131 131 return True
132 132 elif ( len(self.input_buffer.split('\n'))>2
133 133 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
134 134 return False
135 135 else:
136 136 # Add line returns here, to make sure that the statement is
137 137 # complete.
138 138 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
139 139
140 140
141 141 def write(self, string):
142 142 """ Write some characters to the display.
143 143
144 144 Subclass should overide this method.
145 145 """
146 146 print >>sys.__stderr__, string
147 147
148 148
149 149 def execute(self, python_string, raw_string=None):
150 150 """ Stores the raw_string in the history, and sends the
151 151 python string to the interpreter.
152 152 """
153 153 if raw_string is None:
154 154 raw_string = python_string
155 155 # Create a false result, in case there is an exception
156 156 self.last_result = dict(number=self.prompt_number)
157 157 try:
158 158 self.history.input_cache[-1] = raw_string.rstrip()
159 159 result = self.shell.execute(python_string)
160 160 self.last_result = result
161 161 self.render_result(result)
162 162 except:
163 163 self.show_traceback()
164 164 finally:
165 165 self.after_execute()
166 166
167 167 #--------------------------------------------------------------------------
168 168 # LineFrontEndBase interface
169 169 #--------------------------------------------------------------------------
170 170
171 171 def prefilter_input(self, string):
172 172 """ Priflter the input to turn it in valid python.
173 173 """
174 174 string = string.replace('\r\n', '\n')
175 175 string = string.replace('\t', 4*' ')
176 176 # Clean the trailing whitespace
177 177 string = '\n'.join(l.rstrip() for l in string.split('\n'))
178 178 return string
179 179
180 180
181 181 def after_execute(self):
182 182 """ All the operations required after an execution to put the
183 183 terminal back in a shape where it is usable.
184 184 """
185 185 self.prompt_number += 1
186 186 self.new_prompt(self.input_prompt_template.substitute(
187 187 number=(self.last_result['number'] + 1)))
188 188 # Start a new empty history entry
189 189 self._add_history(None, '')
190 190 self.history_cursor = len(self.history.input_cache) - 1
191 191
192 192
193 193 def complete_current_input(self):
194 194 """ Do code completion on current line.
195 195 """
196 196 if self.debug:
197 197 print >>sys.__stdout__, "complete_current_input",
198 198 line = self.input_buffer
199 199 new_line, completions = self.complete(line)
200 200 if len(completions)>1:
201 201 self.write_completion(completions)
202 202 self.input_buffer = new_line
203 203 if self.debug:
204 204 print >>sys.__stdout__, completions
205 205
206 206
207 def get_line_width(self):
208 """ Return the width of the line in characters.
209 """
210 return 80
211
212
207 213 def write_completion(self, possibilities):
208 214 """ Write the list of possible completions.
209 215 """
210 216 current_buffer = self.input_buffer
211 217
212 218 self.write('\n')
213 219 max_len = len(max(possibilities, key=len)) + 1
214 220
215 #now we check how much symbol we can put on a line...
216 chars_per_line =self.get_line_width()
221 # Now we check how much symbol we can put on a line...
222 chars_per_line = self.get_line_width()
217 223 symbols_per_line = max(1, chars_per_line/max_len)
218 224
219 225 pos = 1
220 226 buf = []
221 227 for symbol in possibilities:
222 228 if pos < symbols_per_line:
223 229 buf.append(symbol.ljust(max_len))
224 230 pos += 1
225 231 else:
226 232 buf.append(symbol.rstrip() + '\n')
227 233 pos = 1
228 234 self.write(''.join(buf))
229 235 self.new_prompt(self.input_prompt_template.substitute(
230 236 number=self.last_result['number'] + 1))
231 237 self.input_buffer = current_buffer
232 238
233 239
234
235 240 def new_prompt(self, prompt):
236 241 """ Prints a prompt and starts a new editing buffer.
237 242
238 243 Subclasses should use this method to make sure that the
239 244 terminal is put in a state favorable for a new line
240 245 input.
241 246 """
242 247 self.input_buffer = ''
243 248 self.write(prompt)
244 249
245 250
246 251 #--------------------------------------------------------------------------
247 252 # Private API
248 253 #--------------------------------------------------------------------------
249 254
250 255 def _on_enter(self):
251 256 """ Called when the return key is pressed in a line editing
252 257 buffer.
253 258 """
254 259 current_buffer = self.input_buffer
255 260 cleaned_buffer = self.prefilter_input(current_buffer)
256 261 if self.is_complete(cleaned_buffer):
257 262 self.execute(cleaned_buffer, raw_string=current_buffer)
258 263 else:
259 264 self.input_buffer += self._get_indent_string(
260 265 current_buffer[:-1])
261 266 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
262 267 self.input_buffer += '\t'
263 268
264 269
265 270 def _get_indent_string(self, string):
266 271 """ Return the string of whitespace that prefixes a line. Used to
267 272 add the right amount of indendation when creating a new line.
268 273 """
269 274 string = string.replace('\t', ' '*4)
270 275 string = string.split('\n')[-1]
271 276 indent_chars = len(string) - len(string.lstrip())
272 277 indent_string = '\t'*(indent_chars // 4) + \
273 278 ' '*(indent_chars % 4)
274 279
275 280 return indent_string
276 281
277 282
@@ -1,125 +1,129 b''
1 1 # encoding: utf-8
2 2 """
3 3 Test process execution and IO redirection.
4 4 """
5 5
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is
12 12 # in the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
16 16 from cStringIO import StringIO
17 17 import string
18 18
19 19 class TestPrefilterFrontEnd(PrefilterFrontEnd):
20 20
21 21 input_prompt_template = string.Template('')
22 22 output_prompt_template = string.Template('')
23 23
24 24 def __init__(self):
25 25 self.out = StringIO()
26 26 PrefilterFrontEnd.__init__(self)
27 27
28 28 def write(self, string):
29 29 self.out.write(string)
30 30
31 31 def _on_enter(self):
32 32 self.input_buffer += '\n'
33 33 PrefilterFrontEnd._on_enter(self)
34 34
35 35
36 36 def test_execution():
37 37 """ Test execution of a command.
38 38 """
39 39 f = TestPrefilterFrontEnd()
40 40 f.input_buffer = 'print 1\n'
41 41 f._on_enter()
42 42 assert f.out.getvalue() == '1\n'
43 43
44 44
45 45 def test_multiline():
46 46 """ Test execution of a multiline command.
47 47 """
48 48 f = TestPrefilterFrontEnd()
49 49 f.input_buffer = 'if True:'
50 50 f._on_enter()
51 51 f.input_buffer += 'print 1'
52 52 f._on_enter()
53 53 assert f.out.getvalue() == ''
54 54 f._on_enter()
55 55 assert f.out.getvalue() == '1\n'
56 56 f = TestPrefilterFrontEnd()
57 57 f.input_buffer='(1 +'
58 58 f._on_enter()
59 59 f.input_buffer += '0)'
60 60 f._on_enter()
61 61 assert f.out.getvalue() == ''
62 62 f._on_enter()
63 63 assert f.out.getvalue() == '1\n'
64 64
65 65
66 66 def test_capture():
67 67 """ Test the capture of output in different channels.
68 68 """
69 69 # Test on the OS-level stdout, stderr.
70 70 f = TestPrefilterFrontEnd()
71 71 f.input_buffer = \
72 72 'import os; out=os.fdopen(1, "w"); out.write("1") ; out.flush()'
73 73 f._on_enter()
74 74 assert f.out.getvalue() == '1'
75 75 f = TestPrefilterFrontEnd()
76 76 f.input_buffer = \
77 77 'import os; out=os.fdopen(2, "w"); out.write("1") ; out.flush()'
78 78 f._on_enter()
79 79 assert f.out.getvalue() == '1'
80 80
81 81
82 82 def test_magic():
83 83 """ Test the magic expansion and history.
84 84
85 85 This test is fairly fragile and will break when magics change.
86 86 """
87 87 f = TestPrefilterFrontEnd()
88 88 f.input_buffer += '%who\n'
89 89 f._on_enter()
90 90 assert f.out.getvalue() == 'Interactive namespace is empty.\n'
91 91
92 92
93 93 def test_help():
94 94 """ Test object inspection.
95 95 """
96 96 f = TestPrefilterFrontEnd()
97 97 f.input_buffer += "def f():"
98 98 f._on_enter()
99 99 f.input_buffer += "'foobar'"
100 100 f._on_enter()
101 101 f.input_buffer += "pass"
102 102 f._on_enter()
103 103 f._on_enter()
104 104 f.input_buffer += "f?"
105 105 f._on_enter()
106 106 assert f.out.getvalue().split()[-1] == 'foobar'
107 107
108 108
109 109 def test_completion():
110 110 """ Test command-line completion.
111 111 """
112 112 f = TestPrefilterFrontEnd()
113 113 f.input_buffer = 'zzza = 1'
114 114 f._on_enter()
115 115 f.input_buffer = 'zzzb = 2'
116 116 f._on_enter()
117 117 f.input_buffer = 'zz'
118 f.complete_current_input()
119 assert f.out.getvalue() == '\nzzza zzzb '
120 assert f.input_buffer == 'zzz'
118 121
119 122
120 123 if __name__ == '__main__':
121 124 test_magic()
122 125 test_help()
123 126 test_execution()
124 127 test_multiline()
125 128 test_capture()
129 test_completion()
General Comments 0
You need to be logged in to leave comments. Login now