##// END OF EJS Templates
First cut of subprocess execution with redirection of stdin/stdout.
Gael Varoquaux -
Show More
@@ -0,0 +1,48 b''
1 # encoding: utf-8
2 """
3 Object for encapsulating process execution by using callbacks for stdout,
4 stderr and stdin.
5 """
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18 from subprocess import Popen, PIPE
19 from threading import Thread
20
21
22 class PipedProcess(Thread):
23
24 def __init__(self, command_string, out_callback,
25 end_callback=None,):
26 self.command_string = command_string
27 self.out_callback = out_callback
28 self.end_callback = end_callback
29 Thread.__init__(self)
30
31
32 def run(self):
33 """ Start the process and hook up the callbacks.
34 """
35 process = Popen((self.command_string + ' 2>&1', ), shell=True,
36 universal_newlines=True,
37 stdout=PIPE, stdin=PIPE)
38 self.process = process
39 while True:
40 out_char = process.stdout.read(1)
41 if out_char == '' and process.poll() is not None:
42 break
43 self.out_callback(out_char)
44
45 if self.end_callback is not None:
46 self.end_callback()
47
48
@@ -23,26 +23,19 b' from IPython.ipmaker import make_IPython'
23 from IPython.ipapi import IPApi
23 from IPython.ipapi import IPApi
24 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
24 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
25
25
26 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
27
28 from IPython.ultraTB import ColorTB
26 from IPython.genutils import Term
29 from IPython.genutils import Term
27 import pydoc
30 import pydoc
28
31
29 #-------------------------------------------------------------------------------
32 def mk_system_call(system_call_function, command):
30 # Utility functions (temporary, should be moved out of here)
33 """ given a os.system replacement, and a leading string command,
31 #-------------------------------------------------------------------------------
34 returns a function that will execute the command with the given
32 import os
35 argument string.
33 def xterm_system(command):
34 """ Run a command in a separate console window.
35 """
36 os.system(("""xterm -title "%s" -e \'/bin/sh -c "%s ; """
37 """echo; echo press enter to close ; """
38 # """echo \\"\x1b]0;%s (finished -- press enter to close)\x07\\" ;
39 """read foo;"\' """) % (command, command) )
40
41 def system_call(command):
42 """ Temporary hack for aliases
43 """
36 """
44 def my_system_call(args):
37 def my_system_call(args):
45 os.system("%s %s" % (command, args))
38 system_call_function("%s %s" % (command, args))
46 return my_system_call
39 return my_system_call
47
40
48 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
@@ -65,14 +58,18 b' class PrefilterFrontEnd(LineFrontEndBase):'
65 self.shell.user_global_ns = self.ipython0.user_global_ns
58 self.shell.user_global_ns = self.ipython0.user_global_ns
66 # Make sure the raw system call doesn't get called, as we don't
59 # Make sure the raw system call doesn't get called, as we don't
67 # have a stdin accessible.
60 # have a stdin accessible.
68 self._ip.system = xterm_system
61 self._ip.system = self.system_call
69 # XXX: Muck around with magics so that they work better
62 # XXX: Muck around with magics so that they work better
70 # in our environment
63 # in our environment
71 self.ipython0.magic_ls = system_call('ls -CF')
64 self.ipython0.magic_ls = mk_system_call(self.system_call,
65 'ls -CF')
72 self.shell.output_trap = RedirectorOutputTrap(
66 self.shell.output_trap = RedirectorOutputTrap(
73 out_callback=self.write,
67 out_callback=self.write,
74 err_callback=self.write,
68 err_callback=self.write,
75 )
69 )
70 self.shell.traceback_trap = SyncTracebackTrap(
71 formatters=[ColorTB(color_scheme='LightBG'), ]
72 )
76 # Capture and release the outputs, to make sure all the
73 # Capture and release the outputs, to make sure all the
77 # shadow variables are set
74 # shadow variables are set
78 self.capture_output()
75 self.capture_output()
@@ -115,6 +112,13 b' class PrefilterFrontEnd(LineFrontEndBase):'
115 self.release_output()
112 self.release_output()
116
113
117
114
115 def system_call(self, command):
116 """ Allows for frontend to define their own system call, to be
117 able capture output and redirect input.
118 """
119 return os.system(command, args)
120
121
118 def capture_output(self):
122 def capture_output(self):
119 """ Capture all the output mechanisms we can think of.
123 """ Capture all the output mechanisms we can think of.
120 """
124 """
@@ -198,9 +198,11 b' class ConsoleWidget(editwindow.EditWindow):'
198 """ Write given text to buffer, while translating the ansi escape
198 """ Write given text to buffer, while translating the ansi escape
199 sequences.
199 sequences.
200 """
200 """
201 # XXX: do not put print statements in this method, the print
201 # XXX: do not put print statements to sys.stdout/sys.stderr in
202 # statements will call this method, and you will end up with
202 # this method, the print statements will call this method, as
203 # an infinit loop
203 # you will end up with an infinit loop
204 if self.debug:
205 print >>sys.__stderr__, text
204 title = self.title_pat.split(text)
206 title = self.title_pat.split(text)
205 if len(title)>1:
207 if len(title)>1:
206 self.title = title[-2]
208 self.title = title[-2]
@@ -5,6 +5,7 b' ipython.'
5
5
6 import wx
6 import wx
7 from wx_frontend import WxController
7 from wx_frontend import WxController
8 import __builtin__
8
9
9 class WIPythonController(WxController):
10 class WIPythonController(WxController):
10 """ Sub class of WxController that adds some application-specific
11 """ Sub class of WxController that adds some application-specific
@@ -19,7 +20,8 b' class WIPythonController(WxController):'
19 def _on_key_down(self, event, skip=True):
20 def _on_key_down(self, event, skip=True):
20 # Intercept Ctrl-D to quit
21 # Intercept Ctrl-D to quit
21 if event.KeyCode == ord('D') and event.ControlDown() and \
22 if event.KeyCode == ord('D') and event.ControlDown() and \
22 self.get_current_edit_buffer()=='':
23 self.get_current_edit_buffer()=='' and \
24 not self.raw_input == __builtin__.raw_input:
23 wx.CallAfter(self.ask_exit)
25 wx.CallAfter(self.ask_exit)
24 else:
26 else:
25 WxController._on_key_down(self, event, skip=skip)
27 WxController._on_key_down(self, event, skip=skip)
@@ -27,7 +27,11 b' from wx import stc'
27 from console_widget import ConsoleWidget
27 from console_widget import ConsoleWidget
28 import __builtin__
28 import __builtin__
29 from time import sleep
29 from time import sleep
30 import sys
30
31
32 from threading import Lock
33
34 from IPython.frontend.piped_process import PipedProcess
31 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
35 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
32
36
33 #_COMMAND_BG = '#FAFAF1' # Nice green
37 #_COMMAND_BG = '#FAFAF1' # Nice green
@@ -46,7 +50,19 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
46 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
50 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
47
51
48 debug = True
52 debug = True
49
53
54 # Attribute to store reference to the pipes of a subprocess, if we
55 # are running any.
56 running_process = False
57
58 # A queue for writing fast streams to the screen without flooding the
59 # event loop
60 write_buffer = []
61
62 # A lock to lock the write_buffer to make sure we don't empty it
63 # while it is being swapped
64 write_buffer_lock = Lock()
65
50 #--------------------------------------------------------------------------
66 #--------------------------------------------------------------------------
51 # Public API
67 # Public API
52 #--------------------------------------------------------------------------
68 #--------------------------------------------------------------------------
@@ -59,9 +75,6 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
59 ConsoleWidget.__init__(self, parent, id, pos, size, style)
75 ConsoleWidget.__init__(self, parent, id, pos, size, style)
60 PrefilterFrontEnd.__init__(self)
76 PrefilterFrontEnd.__init__(self)
61
77
62 # Capture Character keys
63 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
64
65 # Marker for running buffer.
78 # Marker for running buffer.
66 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
79 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
67 background=_RUNNING_BUFFER_BG)
80 background=_RUNNING_BUFFER_BG)
@@ -69,18 +82,28 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
69 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
82 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
70 background=_ERROR_BG)
83 background=_ERROR_BG)
71
84
85 # A time for flushing the write buffer
86 BUFFER_FLUSH_TIMER_ID = 100
87 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
88 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
72
89
73 def do_completion(self):
90 def do_completion(self):
74 """ Do code completion.
91 """ Do code completion.
75 """
92 """
93 if self.debug:
94 print >>sys.__stdout__, "do_completion",
76 line = self.get_current_edit_buffer()
95 line = self.get_current_edit_buffer()
77 new_line, completions = self.complete(line)
96 new_line, completions = self.complete(line)
78 if len(completions)>1:
97 if len(completions)>1:
79 self.write_completion(completions)
98 self.write_completion(completions)
80 self.replace_current_edit_buffer(new_line)
99 self.replace_current_edit_buffer(new_line)
100 if self.debug:
101 print >>sys.__stdout__, completions
81
102
82
103
83 def do_calltip(self):
104 def do_calltip(self):
105 if self.debug:
106 print >>sys.__stdout__, "do_calltip"
84 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
107 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
85 symbol = self.get_current_edit_buffer()
108 symbol = self.get_current_edit_buffer()
86 symbol_string = separators.split(symbol)[-1]
109 symbol_string = separators.split(symbol)[-1]
@@ -108,6 +131,8 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
108 """ Updates the popup completion menu if it exists. If create is
131 """ Updates the popup completion menu if it exists. If create is
109 true, open the menu.
132 true, open the menu.
110 """
133 """
134 if self.debug:
135 print >>sys.__stdout__, "popup_completion",
111 line = self.get_current_edit_buffer()
136 line = self.get_current_edit_buffer()
112 if (self.AutoCompActive() and not line[-1] == '.') \
137 if (self.AutoCompActive() and not line[-1] == '.') \
113 or create==True:
138 or create==True:
@@ -118,6 +143,8 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
118 residual = complete_sep.split(line)[-1]
143 residual = complete_sep.split(line)[-1]
119 offset = len(residual)
144 offset = len(residual)
120 self.pop_completion(completions, offset=offset)
145 self.pop_completion(completions, offset=offset)
146 if self.debug:
147 print >>sys.__stdout__, completions
121
148
122
149
123 def raw_input(self, prompt):
150 def raw_input(self, prompt):
@@ -148,9 +175,6 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
148 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
175 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
149 # Update the display:
176 # Update the display:
150 wx.Yield()
177 wx.Yield()
151 ## Remove the trailing "\n" for cleaner display
152 #self.SetSelection(self.GetLength()-1, self.GetLength())
153 #self.ReplaceSelection('')
154 self.GotoPos(self.GetLength())
178 self.GotoPos(self.GetLength())
155 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
179 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
156
180
@@ -171,6 +195,51 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
171 if hasattr(self, '_cursor'):
195 if hasattr(self, '_cursor'):
172 del self._cursor
196 del self._cursor
173
197
198
199 def system_call(self, command_string):
200 self.running_process = PipedProcess(command_string,
201 out_callback=self.buffered_write,
202 end_callback = self._end_system_call)
203 self.running_process.start()
204 # XXX: another one of these polling loops to have a blocking
205 # call
206 while self.running_process:
207 wx.Yield()
208 sleep(0.1)
209
210
211 def buffered_write(self, text):
212 """ A write method for streams, that caches the stream in order
213 to avoid flooding the event loop.
214
215 This can be called outside of the main loop, in separate
216 threads.
217 """
218 self.write_buffer_lock.acquire()
219 self.write_buffer.append(text)
220 self.write_buffer_lock.release()
221 if not self._buffer_flush_timer.IsRunning():
222 self._buffer_flush_timer.Start(100) # milliseconds
223
224
225 def _end_system_call(self):
226 """ Called at the end of a system call.
227 """
228 self.running_process = False
229
230
231 def _buffer_flush(self, event):
232 """ Called by the timer to flush the write buffer.
233
234 This is always called in the mainloop, by the wx timer.
235 """
236 self.write_buffer_lock.acquire()
237 write_buffer = self.write_buffer
238 self.write_buffer = []
239 self.write_buffer_lock.release()
240 self.write(''.join(write_buffer))
241 self._buffer_flush_timer.Stop()
242
174
243
175 def show_traceback(self):
244 def show_traceback(self):
176 start_line = self.GetCurrentLine()
245 start_line = self.GetCurrentLine()
@@ -184,14 +253,20 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
184 # Private API
253 # Private API
185 #--------------------------------------------------------------------------
254 #--------------------------------------------------------------------------
186
255
187
188 def _on_key_down(self, event, skip=True):
256 def _on_key_down(self, event, skip=True):
189 """ Capture the character events, let the parent
257 """ Capture the character events, let the parent
190 widget handle them, and put our logic afterward.
258 widget handle them, and put our logic afterward.
191 """
259 """
192 current_line_number = self.GetCurrentLine()
260 current_line_number = self.GetCurrentLine()
193 # Calltips
261 if self.running_process and event.KeyCode<256 \
194 if event.KeyCode == ord('('):
262 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
263 # We are running a process, let us not be too clever.
264 ConsoleWidget._on_key_down(self, event, skip=skip)
265 if self.debug:
266 print >>sys.__stderr__, chr(event.KeyCode)
267 self.running_process.process.stdin.write(chr(event.KeyCode))
268 elif event.KeyCode in (ord('('), 57):
269 # Calltips
195 event.Skip()
270 event.Skip()
196 self.do_calltip()
271 self.do_calltip()
197 elif self.AutoCompActive():
272 elif self.AutoCompActive():
@@ -234,7 +309,7 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
234
309
235
310
236 def _on_key_up(self, event, skip=True):
311 def _on_key_up(self, event, skip=True):
237 if event.KeyCode == 59:
312 if event.KeyCode in (59, ord('.')):
238 # Intercepting '.'
313 # Intercepting '.'
239 event.Skip()
314 event.Skip()
240 self.popup_completion(create=True)
315 self.popup_completion(create=True)
@@ -243,7 +318,6 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
243
318
244 def _on_enter(self):
319 def _on_enter(self):
245 if self.debug:
320 if self.debug:
246 import sys
247 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
321 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
248 PrefilterFrontEnd._on_enter(self)
322 PrefilterFrontEnd._on_enter(self)
249
323
@@ -14,10 +14,8 b' __docformat__ = "restructuredtext en"'
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
18 import sys
17 import sys
19
18
20
21 class TracebackTrap(object):
19 class TracebackTrap(object):
22 """ Object to trap and format tracebacks.
20 """ Object to trap and format tracebacks.
23 """
21 """
@@ -38,9 +36,6 b' class TracebackTrap(object):'
38 def hook(self, *args):
36 def hook(self, *args):
39 """ This method actually implements the hook.
37 """ This method actually implements the hook.
40 """
38 """
41 import sys
42 print >>sys.stderr, "I have been raised"
43
44 self.args = args
39 self.args = args
45
40
46 def set(self):
41 def set(self):
General Comments 0
You need to be logged in to leave comments. Login now