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