##// END OF EJS Templates
Merge pull request #1945 from tkf/script-proc...
Min RK -
r7567:920ee654 merge
parent child Browse files
Show More
@@ -1,216 +1,247 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 ),
63 magic_arguments.argument(
64 '--proc', type=str,
65 help="""The variable in which to store Popen instance.
66 This is used only when --bg option is given.
67 """
68 ),
61 ]
69 ]
62 for arg in args:
70 for arg in args:
63 f = arg(f)
71 f = arg(f)
64 return f
72 return f
65
73
66 @magics_class
74 @magics_class
67 class ScriptMagics(Magics, Configurable):
75 class ScriptMagics(Magics, Configurable):
68 """Magics for talking to scripts
76 """Magics for talking to scripts
69
77
70 This defines a base `%%script` cell magic for running a cell
78 This defines a base `%%script` cell magic for running a cell
71 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
72 magics that call %%script with common interpreters.
80 magics that call %%script with common interpreters.
73 """
81 """
74 script_magics = List(config=True,
82 script_magics = List(config=True,
75 help="""Extra script cell magics to define
83 help="""Extra script cell magics to define
76
84
77 This generates simple wrappers of `%%script foo` as `%%foo`.
85 This generates simple wrappers of `%%script foo` as `%%foo`.
78
86
79 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,
80 specify them in script_paths
88 specify them in script_paths
81 """,
89 """,
82 )
90 )
83 def _script_magics_default(self):
91 def _script_magics_default(self):
84 """default to a common list of programs if we find them"""
92 """default to a common list of programs if we find them"""
85
93
86 defaults = []
94 defaults = []
87 to_try = []
95 to_try = []
88 if os.name == 'nt':
96 if os.name == 'nt':
89 defaults.append('cmd')
97 defaults.append('cmd')
90 to_try.append('powershell')
98 to_try.append('powershell')
91 to_try.extend([
99 to_try.extend([
92 'sh',
100 'sh',
93 'bash',
101 'bash',
94 'perl',
102 'perl',
95 'ruby',
103 'ruby',
96 'python3',
104 'python3',
97 'pypy',
105 'pypy',
98 ])
106 ])
99
107
100 for cmd in to_try:
108 for cmd in to_try:
101 if cmd in self.script_paths:
109 if cmd in self.script_paths:
102 defaults.append(cmd)
110 defaults.append(cmd)
103 else:
111 else:
104 try:
112 try:
105 find_cmd(cmd)
113 find_cmd(cmd)
106 except FindCmdError:
114 except FindCmdError:
107 # command not found, ignore it
115 # command not found, ignore it
108 pass
116 pass
109 except ImportError:
117 except ImportError:
110 # Windows without pywin32, find_cmd doesn't work
118 # Windows without pywin32, find_cmd doesn't work
111 pass
119 pass
112 else:
120 else:
113 defaults.append(cmd)
121 defaults.append(cmd)
114 return defaults
122 return defaults
115
123
116 script_paths = Dict(config=True,
124 script_paths = Dict(config=True,
117 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'
118
126
119 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
120 find the right interpreter.
128 find the right interpreter.
121 """
129 """
122 )
130 )
123
131
124 def __init__(self, shell=None):
132 def __init__(self, shell=None):
125 Configurable.__init__(self, config=shell.config)
133 Configurable.__init__(self, config=shell.config)
126 self._generate_script_magics()
134 self._generate_script_magics()
127 Magics.__init__(self, shell=shell)
135 Magics.__init__(self, shell=shell)
128 self.job_manager = BackgroundJobManager()
136 self.job_manager = BackgroundJobManager()
129
137
130 def _generate_script_magics(self):
138 def _generate_script_magics(self):
131 cell_magics = self.magics['cell']
139 cell_magics = self.magics['cell']
132 for name in self.script_magics:
140 for name in self.script_magics:
133 cell_magics[name] = self._make_script_magic(name)
141 cell_magics[name] = self._make_script_magic(name)
134
142
135 def _make_script_magic(self, name):
143 def _make_script_magic(self, name):
136 """make a named magic, that calls %%script with a particular program"""
144 """make a named magic, that calls %%script with a particular program"""
137 # expand to explicit path if necessary:
145 # expand to explicit path if necessary:
138 script = self.script_paths.get(name, name)
146 script = self.script_paths.get(name, name)
139
147
140 @magic_arguments.magic_arguments()
148 @magic_arguments.magic_arguments()
141 @script_args
149 @script_args
142 def named_script_magic(line, cell):
150 def named_script_magic(line, cell):
143 # if line, add it as cl-flags
151 # if line, add it as cl-flags
144 if line:
152 if line:
145 line = "%s %s" % (script, line)
153 line = "%s %s" % (script, line)
146 else:
154 else:
147 line = script
155 line = script
148 return self.shebang(line, cell)
156 return self.shebang(line, cell)
149
157
150 # write a basic docstring:
158 # write a basic docstring:
151 named_script_magic.__doc__ = \
159 named_script_magic.__doc__ = \
152 """%%{name} script magic
160 """%%{name} script magic
153
161
154 Run cells with {script} in a subprocess.
162 Run cells with {script} in a subprocess.
155
163
156 This is a shortcut for `%%script {script}`
164 This is a shortcut for `%%script {script}`
157 """.format(**locals())
165 """.format(**locals())
158
166
159 return named_script_magic
167 return named_script_magic
160
168
161 @magic_arguments.magic_arguments()
169 @magic_arguments.magic_arguments()
162 @script_args
170 @script_args
163 @cell_magic("script")
171 @cell_magic("script")
164 def shebang(self, line, cell):
172 def shebang(self, line, cell):
165 """Run a cell via a shell command
173 """Run a cell via a shell command
166
174
167 The `%%script` line is like the #! line of script,
175 The `%%script` line is like the #! line of script,
168 specifying a program (bash, perl, ruby, etc.) with which to run.
176 specifying a program (bash, perl, ruby, etc.) with which to run.
169
177
170 The rest of the cell is run by that program.
178 The rest of the cell is run by that program.
171
179
172 Examples
180 Examples
173 --------
181 --------
174 ::
182 ::
175
183
176 In [1]: %%script bash
184 In [1]: %%script bash
177 ...: for i in 1 2 3; do
185 ...: for i in 1 2 3; do
178 ...: echo $i
186 ...: echo $i
179 ...: done
187 ...: done
180 1
188 1
181 2
189 2
182 3
190 3
183 """
191 """
184 argv = arg_split(line, posix = not sys.platform.startswith('win'))
192 argv = arg_split(line, posix = not sys.platform.startswith('win'))
185 args, cmd = self.shebang.parser.parse_known_args(argv)
193 args, cmd = self.shebang.parser.parse_known_args(argv)
186
194
187 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
195 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
188
196
189 cell = cell.encode('utf8', 'replace')
197 cell = cell.encode('utf8', 'replace')
190 if args.bg:
198 if args.bg:
191 if args.out:
199 if args.out:
192 self.shell.user_ns[args.out] = p.stdout
200 self.shell.user_ns[args.out] = p.stdout
193 if args.err:
201 if args.err:
194 self.shell.user_ns[args.err] = p.stderr
202 self.shell.user_ns[args.err] = p.stderr
195 self.job_manager.new(self._run_script, p, cell)
203 self.job_manager.new(self._run_script, p, cell)
204 if args.proc:
205 self.shell.user_ns[args.proc] = p
196 return
206 return
197
207
198 out, err = p.communicate(cell)
208 try:
209 out, err = p.communicate(cell)
210 except KeyboardInterrupt:
211 try:
212 p.send_signal(signal.SIGINT)
213 time.sleep(0.1)
214 if p.poll() is not None:
215 print "Process is interrupted."
216 return
217 p.terminate()
218 time.sleep(0.1)
219 if p.poll() is not None:
220 print "Process is terminated."
221 return
222 p.kill()
223 print "Process is killed."
224 except OSError:
225 pass
226 except Exception as e:
227 print "Error while terminating subprocess (pid=%i): %s" \
228 % (p.pid, e)
229 return
199 out = py3compat.bytes_to_str(out)
230 out = py3compat.bytes_to_str(out)
200 err = py3compat.bytes_to_str(err)
231 err = py3compat.bytes_to_str(err)
201 if args.out:
232 if args.out:
202 self.shell.user_ns[args.out] = out
233 self.shell.user_ns[args.out] = out
203 else:
234 else:
204 sys.stdout.write(out)
235 sys.stdout.write(out)
205 sys.stdout.flush()
236 sys.stdout.flush()
206 if args.err:
237 if args.err:
207 self.shell.user_ns[args.err] = err
238 self.shell.user_ns[args.err] = err
208 else:
239 else:
209 sys.stderr.write(err)
240 sys.stderr.write(err)
210 sys.stderr.flush()
241 sys.stderr.flush()
211
242
212 def _run_script(self, p, cell):
243 def _run_script(self, p, cell):
213 """callback for running the script in the background"""
244 """callback for running the script in the background"""
214 p.stdin.write(cell)
245 p.stdin.write(cell)
215 p.stdin.close()
246 p.stdin.close()
216 p.wait()
247 p.wait()
General Comments 0
You need to be logged in to leave comments. Login now