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