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