##// END OF EJS Templates
skip find_cmd when setting up script magics...
MinRK -
Show More
@@ -1,288 +1,275 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 from IPython.utils.process import find_cmd, FindCmdError, arg_split
33 from IPython.utils.process import 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 """default to a common list of programs if we find them"""
93 """default to a common list of programs"""
94 94
95 defaults = []
96 to_try = []
97 if os.name == 'nt':
98 defaults.append('cmd')
99 to_try.append('powershell')
100 to_try.extend([
95 defaults = [
101 96 'sh',
102 97 'bash',
103 98 'perl',
104 99 'ruby',
100 'python',
105 101 'python3',
106 102 'pypy',
103 ]
104 if os.name == 'nt':
105 defaults.extend([
106 'cmd',
107 'powershell',
107 108 ])
108 109
109 for cmd in to_try:
110 if cmd in self.script_paths:
111 defaults.append(cmd)
112 else:
113 try:
114 find_cmd(cmd)
115 except FindCmdError:
116 # command not found, ignore it
117 pass
118 except ImportError:
119 # Windows without pywin32, find_cmd doesn't work
120 pass
121 else:
122 defaults.append(cmd)
123 110 return defaults
124 111
125 112 script_paths = Dict(config=True,
126 113 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
127 114
128 115 Only necessary for items in script_magics where the default path will not
129 116 find the right interpreter.
130 117 """
131 118 )
132 119
133 120 def __init__(self, shell=None):
134 121 Configurable.__init__(self, config=shell.config)
135 122 self._generate_script_magics()
136 123 Magics.__init__(self, shell=shell)
137 124 self.job_manager = BackgroundJobManager()
138 125 self.bg_processes = []
139 126 atexit.register(self.kill_bg_processes)
140 127
141 128 def __del__(self):
142 129 self.kill_bg_processes()
143 130
144 131 def _generate_script_magics(self):
145 132 cell_magics = self.magics['cell']
146 133 for name in self.script_magics:
147 134 cell_magics[name] = self._make_script_magic(name)
148 135
149 136 def _make_script_magic(self, name):
150 137 """make a named magic, that calls %%script with a particular program"""
151 138 # expand to explicit path if necessary:
152 139 script = self.script_paths.get(name, name)
153 140
154 141 @magic_arguments.magic_arguments()
155 142 @script_args
156 143 def named_script_magic(line, cell):
157 144 # if line, add it as cl-flags
158 145 if line:
159 146 line = "%s %s" % (script, line)
160 147 else:
161 148 line = script
162 149 return self.shebang(line, cell)
163 150
164 151 # write a basic docstring:
165 152 named_script_magic.__doc__ = \
166 153 """%%{name} script magic
167 154
168 155 Run cells with {script} in a subprocess.
169 156
170 157 This is a shortcut for `%%script {script}`
171 158 """.format(**locals())
172 159
173 160 return named_script_magic
174 161
175 162 @magic_arguments.magic_arguments()
176 163 @script_args
177 164 @cell_magic("script")
178 165 def shebang(self, line, cell):
179 166 """Run a cell via a shell command
180 167
181 168 The `%%script` line is like the #! line of script,
182 169 specifying a program (bash, perl, ruby, etc.) with which to run.
183 170
184 171 The rest of the cell is run by that program.
185 172
186 173 Examples
187 174 --------
188 175 ::
189 176
190 177 In [1]: %%script bash
191 178 ...: for i in 1 2 3; do
192 179 ...: echo $i
193 180 ...: done
194 181 1
195 182 2
196 183 3
197 184 """
198 185 argv = arg_split(line, posix = not sys.platform.startswith('win'))
199 186 args, cmd = self.shebang.parser.parse_known_args(argv)
200 187
201 188 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
202 189
203 190 cell = cell.encode('utf8', 'replace')
204 191 if args.bg:
205 192 self.bg_processes.append(p)
206 193 self._gc_bg_processes()
207 194 if args.out:
208 195 self.shell.user_ns[args.out] = p.stdout
209 196 if args.err:
210 197 self.shell.user_ns[args.err] = p.stderr
211 198 self.job_manager.new(self._run_script, p, cell, daemon=True)
212 199 if args.proc:
213 200 self.shell.user_ns[args.proc] = p
214 201 return
215 202
216 203 try:
217 204 out, err = p.communicate(cell)
218 205 except KeyboardInterrupt:
219 206 try:
220 207 p.send_signal(signal.SIGINT)
221 208 time.sleep(0.1)
222 209 if p.poll() is not None:
223 210 print "Process is interrupted."
224 211 return
225 212 p.terminate()
226 213 time.sleep(0.1)
227 214 if p.poll() is not None:
228 215 print "Process is terminated."
229 216 return
230 217 p.kill()
231 218 print "Process is killed."
232 219 except OSError:
233 220 pass
234 221 except Exception as e:
235 222 print "Error while terminating subprocess (pid=%i): %s" \
236 223 % (p.pid, e)
237 224 return
238 225 out = py3compat.bytes_to_str(out)
239 226 err = py3compat.bytes_to_str(err)
240 227 if args.out:
241 228 self.shell.user_ns[args.out] = out
242 229 else:
243 230 sys.stdout.write(out)
244 231 sys.stdout.flush()
245 232 if args.err:
246 233 self.shell.user_ns[args.err] = err
247 234 else:
248 235 sys.stderr.write(err)
249 236 sys.stderr.flush()
250 237
251 238 def _run_script(self, p, cell):
252 239 """callback for running the script in the background"""
253 240 p.stdin.write(cell)
254 241 p.stdin.close()
255 242 p.wait()
256 243
257 244 @line_magic("killbgscripts")
258 245 def killbgscripts(self, _nouse_=''):
259 246 """Kill all BG processes started by %%script and its family."""
260 247 self.kill_bg_processes()
261 248 print "All background processes were killed."
262 249
263 250 def kill_bg_processes(self):
264 251 """Kill all BG processes which are still running."""
265 252 for p in self.bg_processes:
266 253 if p.poll() is None:
267 254 try:
268 255 p.send_signal(signal.SIGINT)
269 256 except:
270 257 pass
271 258 time.sleep(0.1)
272 259 for p in self.bg_processes:
273 260 if p.poll() is None:
274 261 try:
275 262 p.terminate()
276 263 except:
277 264 pass
278 265 time.sleep(0.1)
279 266 for p in self.bg_processes:
280 267 if p.poll() is None:
281 268 try:
282 269 p.kill()
283 270 except:
284 271 pass
285 272 self._gc_bg_processes()
286 273
287 274 def _gc_bg_processes(self):
288 275 self.bg_processes = [p for p in self.bg_processes if p.poll() is None]
General Comments 0
You need to be logged in to leave comments. Login now