##// END OF EJS Templates
Make %killbgscripts bit more verbose
Takafumi Arakaki -
Show More
@@ -1,278 +1,283 b''
1 1 """Magic functions for running cells in various scripts."""
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (c) 2012 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the Modified BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 # Stdlib
15 15 import os
16 16 import re
17 17 import sys
18 18 import signal
19 19 import time
20 20 from subprocess import Popen, PIPE
21 21 import atexit
22 22
23 23 # Our own packages
24 24 from IPython.config.configurable import Configurable
25 25 from IPython.core import magic_arguments
26 26 from IPython.core.error import UsageError
27 27 from IPython.core.magic import (
28 28 Magics, magics_class, line_magic, cell_magic
29 29 )
30 30 from IPython.lib.backgroundjobs import BackgroundJobManager
31 31 from IPython.testing.skipdoctest import skip_doctest
32 32 from IPython.utils import py3compat
33 33 from IPython.utils.process import find_cmd, FindCmdError, arg_split
34 34 from IPython.utils.traitlets import List, Dict
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Magic implementation classes
38 38 #-----------------------------------------------------------------------------
39 39
40 40 def script_args(f):
41 41 """single decorator for adding script args"""
42 42 args = [
43 43 magic_arguments.argument(
44 44 '--out', type=str,
45 45 help="""The variable in which to store stdout from the script.
46 46 If the script is backgrounded, this will be the stdout *pipe*,
47 47 instead of the stderr text itself.
48 48 """
49 49 ),
50 50 magic_arguments.argument(
51 51 '--err', type=str,
52 52 help="""The variable in which to store stderr from the script.
53 53 If the script is backgrounded, this will be the stderr *pipe*,
54 54 instead of the stderr text itself.
55 55 """
56 56 ),
57 57 magic_arguments.argument(
58 58 '--bg', action="store_true",
59 59 help="""Whether to run the script in the background.
60 60 If given, the only way to see the output of the command is
61 61 with --out/err.
62 62 """
63 63 ),
64 64 magic_arguments.argument(
65 65 '--proc', type=str,
66 66 help="""The variable in which to store Popen instance.
67 67 This is used only when --bg option is given.
68 68 """
69 69 ),
70 70 ]
71 71 for arg in args:
72 72 f = arg(f)
73 73 return f
74 74
75 75 @magics_class
76 76 class ScriptMagics(Magics, Configurable):
77 77 """Magics for talking to scripts
78 78
79 79 This defines a base `%%script` cell magic for running a cell
80 80 with a program in a subprocess, and registers a few top-level
81 81 magics that call %%script with common interpreters.
82 82 """
83 83 script_magics = List(config=True,
84 84 help="""Extra script cell magics to define
85 85
86 86 This generates simple wrappers of `%%script foo` as `%%foo`.
87 87
88 88 If you want to add script magics that aren't on your path,
89 89 specify them in script_paths
90 90 """,
91 91 )
92 92 def _script_magics_default(self):
93 93 """default to a common list of programs if we find them"""
94 94
95 95 defaults = []
96 96 to_try = []
97 97 if os.name == 'nt':
98 98 defaults.append('cmd')
99 99 to_try.append('powershell')
100 100 to_try.extend([
101 101 'sh',
102 102 'bash',
103 103 'perl',
104 104 'ruby',
105 105 'python3',
106 106 'pypy',
107 107 ])
108 108
109 109 for cmd in to_try:
110 110 if cmd in self.script_paths:
111 111 defaults.append(cmd)
112 112 else:
113 113 try:
114 114 find_cmd(cmd)
115 115 except FindCmdError:
116 116 # command not found, ignore it
117 117 pass
118 118 except ImportError:
119 119 # Windows without pywin32, find_cmd doesn't work
120 120 pass
121 121 else:
122 122 defaults.append(cmd)
123 123 return defaults
124 124
125 125 script_paths = Dict(config=True,
126 126 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
127 127
128 128 Only necessary for items in script_magics where the default path will not
129 129 find the right interpreter.
130 130 """
131 131 )
132 132
133 133 def __init__(self, shell=None):
134 134 Configurable.__init__(self, config=shell.config)
135 135 self._generate_script_magics()
136 136 Magics.__init__(self, shell=shell)
137 137 self.job_manager = BackgroundJobManager()
138 138 self.bg_processes = []
139 139 atexit.register(self.kill_bg_processes)
140 140
141 141 def __del__(self):
142 142 self.kill_bg_processes()
143 143
144 144 def _generate_script_magics(self):
145 145 cell_magics = self.magics['cell']
146 146 for name in self.script_magics:
147 147 cell_magics[name] = self._make_script_magic(name)
148 148
149 149 def _make_script_magic(self, name):
150 150 """make a named magic, that calls %%script with a particular program"""
151 151 # expand to explicit path if necessary:
152 152 script = self.script_paths.get(name, name)
153 153
154 154 @magic_arguments.magic_arguments()
155 155 @script_args
156 156 def named_script_magic(line, cell):
157 157 # if line, add it as cl-flags
158 158 if line:
159 159 line = "%s %s" % (script, line)
160 160 else:
161 161 line = script
162 162 return self.shebang(line, cell)
163 163
164 164 # write a basic docstring:
165 165 named_script_magic.__doc__ = \
166 166 """%%{name} script magic
167 167
168 168 Run cells with {script} in a subprocess.
169 169
170 170 This is a shortcut for `%%script {script}`
171 171 """.format(**locals())
172 172
173 173 return named_script_magic
174 174
175 175 @magic_arguments.magic_arguments()
176 176 @script_args
177 177 @cell_magic("script")
178 178 def shebang(self, line, cell):
179 179 """Run a cell via a shell command
180 180
181 181 The `%%script` line is like the #! line of script,
182 182 specifying a program (bash, perl, ruby, etc.) with which to run.
183 183
184 184 The rest of the cell is run by that program.
185 185
186 186 Examples
187 187 --------
188 188 ::
189 189
190 190 In [1]: %%script bash
191 191 ...: for i in 1 2 3; do
192 192 ...: echo $i
193 193 ...: done
194 194 1
195 195 2
196 196 3
197 197 """
198 198 argv = arg_split(line, posix = not sys.platform.startswith('win'))
199 199 args, cmd = self.shebang.parser.parse_known_args(argv)
200 200
201 201 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
202 202
203 203 cell = cell.encode('utf8', 'replace')
204 204 if args.bg:
205 205 self.bg_processes.append(p)
206 206 if args.out:
207 207 self.shell.user_ns[args.out] = p.stdout
208 208 if args.err:
209 209 self.shell.user_ns[args.err] = p.stderr
210 210 self.job_manager.new(self._run_script, p, cell, daemon=True)
211 211 if args.proc:
212 212 self.shell.user_ns[args.proc] = p
213 213 return
214 214
215 215 try:
216 216 out, err = p.communicate(cell)
217 217 except KeyboardInterrupt:
218 218 try:
219 219 p.send_signal(signal.SIGINT)
220 220 time.sleep(0.1)
221 221 if p.poll() is not None:
222 222 print "Process is interrupted."
223 223 return
224 224 p.terminate()
225 225 time.sleep(0.1)
226 226 if p.poll() is not None:
227 227 print "Process is terminated."
228 228 return
229 229 p.kill()
230 230 print "Process is killed."
231 231 except OSError:
232 232 pass
233 233 except Exception as e:
234 234 print "Error while terminating subprocess (pid=%i): %s" \
235 235 % (p.pid, e)
236 236 return
237 237 out = py3compat.bytes_to_str(out)
238 238 err = py3compat.bytes_to_str(err)
239 239 if args.out:
240 240 self.shell.user_ns[args.out] = out
241 241 else:
242 242 sys.stdout.write(out)
243 243 sys.stdout.flush()
244 244 if args.err:
245 245 self.shell.user_ns[args.err] = err
246 246 else:
247 247 sys.stderr.write(err)
248 248 sys.stderr.flush()
249 249
250 250 def _run_script(self, p, cell):
251 251 """callback for running the script in the background"""
252 252 p.stdin.write(cell)
253 253 p.stdin.close()
254 254 p.wait()
255 255
256 256 @line_magic("killbgscripts")
257 def kill_bg_processes(self, dummy=None):
257 def killbgscripts(self, _nouse_=''):
258 """Kill all BG processes started by %%script and its family."""
259 self.kill_bg_processes()
260 print "All background processes were killed."
261
262 def kill_bg_processes(self):
258 263 """Kill all BG processes which are still running."""
259 264 for p in self.bg_processes:
260 265 if p.poll() is None:
261 266 try:
262 267 p.send_signal(signal.SIGINT)
263 268 except:
264 269 pass
265 270 time.sleep(0.1)
266 271 for p in self.bg_processes:
267 272 if p.poll() is None:
268 273 try:
269 274 p.terminate()
270 275 except:
271 276 pass
272 277 time.sleep(0.1)
273 278 for p in self.bg_processes:
274 279 if p.poll() is None:
275 280 try:
276 281 p.kill()
277 282 except:
278 283 pass
General Comments 0
You need to be logged in to leave comments. Login now