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