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