##// 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 23 from IPython.ipapi import IPApi
24 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 29 from IPython.genutils import Term
27 30 import pydoc
28 31
29 #-------------------------------------------------------------------------------
30 # Utility functions (temporary, should be moved out of here)
31 #-------------------------------------------------------------------------------
32 import os
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
32 def mk_system_call(system_call_function, command):
33 """ given a os.system replacement, and a leading string command,
34 returns a function that will execute the command with the given
35 argument string.
43 36 """
44 37 def my_system_call(args):
45 os.system("%s %s" % (command, args))
38 system_call_function("%s %s" % (command, args))
46 39 return my_system_call
47 40
48 41 #-------------------------------------------------------------------------------
@@ -65,14 +58,18 b' class PrefilterFrontEnd(LineFrontEndBase):'
65 58 self.shell.user_global_ns = self.ipython0.user_global_ns
66 59 # Make sure the raw system call doesn't get called, as we don't
67 60 # have a stdin accessible.
68 self._ip.system = xterm_system
61 self._ip.system = self.system_call
69 62 # XXX: Muck around with magics so that they work better
70 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 66 self.shell.output_trap = RedirectorOutputTrap(
73 67 out_callback=self.write,
74 68 err_callback=self.write,
75 69 )
70 self.shell.traceback_trap = SyncTracebackTrap(
71 formatters=[ColorTB(color_scheme='LightBG'), ]
72 )
76 73 # Capture and release the outputs, to make sure all the
77 74 # shadow variables are set
78 75 self.capture_output()
@@ -115,6 +112,13 b' class PrefilterFrontEnd(LineFrontEndBase):'
115 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 122 def capture_output(self):
119 123 """ Capture all the output mechanisms we can think of.
120 124 """
@@ -198,9 +198,11 b' class ConsoleWidget(editwindow.EditWindow):'
198 198 """ Write given text to buffer, while translating the ansi escape
199 199 sequences.
200 200 """
201 # XXX: do not put print statements in this method, the print
202 # statements will call this method, and you will end up with
203 # an infinit loop
201 # XXX: do not put print statements to sys.stdout/sys.stderr in
202 # this method, the print statements will call this method, as
203 # you will end up with an infinit loop
204 if self.debug:
205 print >>sys.__stderr__, text
204 206 title = self.title_pat.split(text)
205 207 if len(title)>1:
206 208 self.title = title[-2]
@@ -5,6 +5,7 b' ipython.'
5 5
6 6 import wx
7 7 from wx_frontend import WxController
8 import __builtin__
8 9
9 10 class WIPythonController(WxController):
10 11 """ Sub class of WxController that adds some application-specific
@@ -19,7 +20,8 b' class WIPythonController(WxController):'
19 20 def _on_key_down(self, event, skip=True):
20 21 # Intercept Ctrl-D to quit
21 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 25 wx.CallAfter(self.ask_exit)
24 26 else:
25 27 WxController._on_key_down(self, event, skip=skip)
@@ -27,7 +27,11 b' from wx import stc'
27 27 from console_widget import ConsoleWidget
28 28 import __builtin__
29 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 35 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
32 36
33 37 #_COMMAND_BG = '#FAFAF1' # Nice green
@@ -46,7 +50,19 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
46 50 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
47 51
48 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 67 # Public API
52 68 #--------------------------------------------------------------------------
@@ -59,9 +75,6 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
59 75 ConsoleWidget.__init__(self, parent, id, pos, size, style)
60 76 PrefilterFrontEnd.__init__(self)
61 77
62 # Capture Character keys
63 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
64
65 78 # Marker for running buffer.
66 79 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
67 80 background=_RUNNING_BUFFER_BG)
@@ -69,18 +82,28 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
69 82 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
70 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 90 def do_completion(self):
74 91 """ Do code completion.
75 92 """
93 if self.debug:
94 print >>sys.__stdout__, "do_completion",
76 95 line = self.get_current_edit_buffer()
77 96 new_line, completions = self.complete(line)
78 97 if len(completions)>1:
79 98 self.write_completion(completions)
80 99 self.replace_current_edit_buffer(new_line)
100 if self.debug:
101 print >>sys.__stdout__, completions
81 102
82 103
83 104 def do_calltip(self):
105 if self.debug:
106 print >>sys.__stdout__, "do_calltip"
84 107 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
85 108 symbol = self.get_current_edit_buffer()
86 109 symbol_string = separators.split(symbol)[-1]
@@ -108,6 +131,8 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
108 131 """ Updates the popup completion menu if it exists. If create is
109 132 true, open the menu.
110 133 """
134 if self.debug:
135 print >>sys.__stdout__, "popup_completion",
111 136 line = self.get_current_edit_buffer()
112 137 if (self.AutoCompActive() and not line[-1] == '.') \
113 138 or create==True:
@@ -118,6 +143,8 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
118 143 residual = complete_sep.split(line)[-1]
119 144 offset = len(residual)
120 145 self.pop_completion(completions, offset=offset)
146 if self.debug:
147 print >>sys.__stdout__, completions
121 148
122 149
123 150 def raw_input(self, prompt):
@@ -148,9 +175,6 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
148 175 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
149 176 # Update the display:
150 177 wx.Yield()
151 ## Remove the trailing "\n" for cleaner display
152 #self.SetSelection(self.GetLength()-1, self.GetLength())
153 #self.ReplaceSelection('')
154 178 self.GotoPos(self.GetLength())
155 179 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
156 180
@@ -171,6 +195,51 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
171 195 if hasattr(self, '_cursor'):
172 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 244 def show_traceback(self):
176 245 start_line = self.GetCurrentLine()
@@ -184,14 +253,20 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
184 253 # Private API
185 254 #--------------------------------------------------------------------------
186 255
187
188 256 def _on_key_down(self, event, skip=True):
189 257 """ Capture the character events, let the parent
190 258 widget handle them, and put our logic afterward.
191 259 """
192 260 current_line_number = self.GetCurrentLine()
193 # Calltips
194 if event.KeyCode == ord('('):
261 if self.running_process and event.KeyCode<256 \
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 270 event.Skip()
196 271 self.do_calltip()
197 272 elif self.AutoCompActive():
@@ -234,7 +309,7 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
234 309
235 310
236 311 def _on_key_up(self, event, skip=True):
237 if event.KeyCode == 59:
312 if event.KeyCode in (59, ord('.')):
238 313 # Intercepting '.'
239 314 event.Skip()
240 315 self.popup_completion(create=True)
@@ -243,7 +318,6 b' class WxController(PrefilterFrontEnd, ConsoleWidget):'
243 318
244 319 def _on_enter(self):
245 320 if self.debug:
246 import sys
247 321 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
248 322 PrefilterFrontEnd._on_enter(self)
249 323
@@ -14,10 +14,8 b' __docformat__ = "restructuredtext en"'
14 14 #-------------------------------------------------------------------------------
15 15 # Imports
16 16 #-------------------------------------------------------------------------------
17
18 17 import sys
19 18
20
21 19 class TracebackTrap(object):
22 20 """ Object to trap and format tracebacks.
23 21 """
@@ -38,9 +36,6 b' class TracebackTrap(object):'
38 36 def hook(self, *args):
39 37 """ This method actually implements the hook.
40 38 """
41 import sys
42 print >>sys.stderr, "I have been raised"
43
44 39 self.args = args
45 40
46 41 def set(self):
General Comments 0
You need to be logged in to leave comments. Login now