##// END OF EJS Templates
Merge pull request #12932 from minrk/pipes-quote...
Matthias Bussonnier -
r26506:bbd5c45a merge
parent child Browse files
Show More
@@ -1,321 +1,321 b''
1 1 """Magic functions for running cells in various scripts."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import errno
7 7 import os
8 8 import sys
9 9 import signal
10 10 import time
11 11 import asyncio
12 12 import atexit
13 13
14 14 from subprocess import CalledProcessError
15 15
16 16 from IPython.core import magic_arguments
17 17 from IPython.core.magic import (
18 18 Magics, magics_class, line_magic, cell_magic
19 19 )
20 20 from IPython.lib.backgroundjobs import BackgroundJobManager
21 21 from IPython.utils.process import arg_split
22 22 from traitlets import List, Dict, default
23 23
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Magic implementation classes
27 27 #-----------------------------------------------------------------------------
28 28
29 29 def script_args(f):
30 30 """single decorator for adding script args"""
31 31 args = [
32 32 magic_arguments.argument(
33 33 '--out', type=str,
34 34 help="""The variable in which to store stdout from the script.
35 35 If the script is backgrounded, this will be the stdout *pipe*,
36 36 instead of the stderr text itself and will not be auto closed.
37 37 """
38 38 ),
39 39 magic_arguments.argument(
40 40 '--err', type=str,
41 41 help="""The variable in which to store stderr from the script.
42 42 If the script is backgrounded, this will be the stderr *pipe*,
43 43 instead of the stderr text itself and will not be autoclosed.
44 44 """
45 45 ),
46 46 magic_arguments.argument(
47 47 '--bg', action="store_true",
48 48 help="""Whether to run the script in the background.
49 49 If given, the only way to see the output of the command is
50 50 with --out/err.
51 51 """
52 52 ),
53 53 magic_arguments.argument(
54 54 '--proc', type=str,
55 55 help="""The variable in which to store Popen instance.
56 56 This is used only when --bg option is given.
57 57 """
58 58 ),
59 59 magic_arguments.argument(
60 60 '--no-raise-error', action="store_false", dest='raise_error',
61 61 help="""Whether you should raise an error message in addition to
62 62 a stream on stderr if you get a nonzero exit code.
63 63 """
64 64 )
65 65 ]
66 66 for arg in args:
67 67 f = arg(f)
68 68 return f
69 69
70 70 @magics_class
71 71 class ScriptMagics(Magics):
72 72 """Magics for talking to scripts
73 73
74 74 This defines a base `%%script` cell magic for running a cell
75 75 with a program in a subprocess, and registers a few top-level
76 76 magics that call %%script with common interpreters.
77 77 """
78 78 script_magics = List(
79 79 help="""Extra script cell magics to define
80 80
81 81 This generates simple wrappers of `%%script foo` as `%%foo`.
82 82
83 83 If you want to add script magics that aren't on your path,
84 84 specify them in script_paths
85 85 """,
86 86 ).tag(config=True)
87 87 @default('script_magics')
88 88 def _script_magics_default(self):
89 89 """default to a common list of programs"""
90 90
91 91 defaults = [
92 92 'sh',
93 93 'bash',
94 94 'perl',
95 95 'ruby',
96 96 'python',
97 97 'python2',
98 98 'python3',
99 99 'pypy',
100 100 ]
101 101 if os.name == 'nt':
102 102 defaults.extend([
103 103 'cmd',
104 104 ])
105 105
106 106 return defaults
107 107
108 108 script_paths = Dict(
109 109 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
110 110
111 111 Only necessary for items in script_magics where the default path will not
112 112 find the right interpreter.
113 113 """
114 114 ).tag(config=True)
115 115
116 116 def __init__(self, shell=None):
117 117 super(ScriptMagics, self).__init__(shell=shell)
118 118 self._generate_script_magics()
119 119 self.job_manager = BackgroundJobManager()
120 120 self.bg_processes = []
121 121 atexit.register(self.kill_bg_processes)
122 122
123 123 def __del__(self):
124 124 self.kill_bg_processes()
125 125
126 126 def _generate_script_magics(self):
127 127 cell_magics = self.magics['cell']
128 128 for name in self.script_magics:
129 129 cell_magics[name] = self._make_script_magic(name)
130 130
131 131 def _make_script_magic(self, name):
132 132 """make a named magic, that calls %%script with a particular program"""
133 133 # expand to explicit path if necessary:
134 134 script = self.script_paths.get(name, name)
135 135
136 136 @magic_arguments.magic_arguments()
137 137 @script_args
138 138 def named_script_magic(line, cell):
139 139 # if line, add it as cl-flags
140 140 if line:
141 141 line = "%s %s" % (script, line)
142 142 else:
143 143 line = script
144 144 return self.shebang(line, cell)
145 145
146 146 # write a basic docstring:
147 147 named_script_magic.__doc__ = \
148 148 """%%{name} script magic
149 149
150 150 Run cells with {script} in a subprocess.
151 151
152 152 This is a shortcut for `%%script {script}`
153 153 """.format(**locals())
154 154
155 155 return named_script_magic
156 156
157 157 @magic_arguments.magic_arguments()
158 158 @script_args
159 159 @cell_magic("script")
160 160 def shebang(self, line, cell):
161 161 """Run a cell via a shell command
162 162
163 163 The `%%script` line is like the #! line of script,
164 164 specifying a program (bash, perl, ruby, etc.) with which to run.
165 165
166 166 The rest of the cell is run by that program.
167 167
168 168 Examples
169 169 --------
170 170 ::
171 171
172 172 In [1]: %%script bash
173 173 ...: for i in 1 2 3; do
174 174 ...: echo $i
175 175 ...: done
176 176 1
177 177 2
178 178 3
179 179 """
180 180
181 181 async def _handle_stream(stream, stream_arg, file_object):
182 182 while True:
183 183 line = (await stream.readline()).decode("utf8")
184 184 if not line:
185 185 break
186 186 if stream_arg:
187 187 self.shell.user_ns[stream_arg] = line
188 188 else:
189 189 file_object.write(line)
190 190 file_object.flush()
191 191
192 192 async def _stream_communicate(process, cell):
193 193 process.stdin.write(cell)
194 194 process.stdin.close()
195 195 stdout_task = asyncio.create_task(
196 196 _handle_stream(process.stdout, args.out, sys.stdout)
197 197 )
198 198 stderr_task = asyncio.create_task(
199 199 _handle_stream(process.stderr, args.err, sys.stderr)
200 200 )
201 201 await asyncio.wait([stdout_task, stderr_task])
202 202 await process.wait()
203 203
204 204 if sys.platform.startswith("win"):
205 205 asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
206 206 loop = asyncio.get_event_loop()
207 207
208 208 argv = arg_split(line, posix=not sys.platform.startswith("win"))
209 209 args, cmd = self.shebang.parser.parse_known_args(argv)
210 210 try:
211 211 p = loop.run_until_complete(
212 212 asyncio.create_subprocess_exec(
213 213 *cmd,
214 214 stdout=asyncio.subprocess.PIPE,
215 215 stderr=asyncio.subprocess.PIPE,
216 stdin=asyncio.subprocess.PIPE
216 stdin=asyncio.subprocess.PIPE,
217 217 )
218 218 )
219 219 except OSError as e:
220 220 if e.errno == errno.ENOENT:
221 221 print("Couldn't find program: %r" % cmd[0])
222 222 return
223 223 else:
224 224 raise
225 225
226 226 if not cell.endswith('\n'):
227 227 cell += '\n'
228 228 cell = cell.encode('utf8', 'replace')
229 229 if args.bg:
230 230 self.bg_processes.append(p)
231 231 self._gc_bg_processes()
232 232 to_close = []
233 233 if args.out:
234 234 self.shell.user_ns[args.out] = p.stdout
235 235 else:
236 236 to_close.append(p.stdout)
237 237 if args.err:
238 238 self.shell.user_ns[args.err] = p.stderr
239 239 else:
240 240 to_close.append(p.stderr)
241 241 self.job_manager.new(self._run_script, p, cell, to_close, daemon=True)
242 242 if args.proc:
243 243 self.shell.user_ns[args.proc] = p
244 244 return
245 245
246 246 try:
247 247 loop.run_until_complete(_stream_communicate(p, cell))
248 248 except KeyboardInterrupt:
249 249 try:
250 250 p.send_signal(signal.SIGINT)
251 251 time.sleep(0.1)
252 252 if p.returncode is not None:
253 253 print("Process is interrupted.")
254 254 return
255 255 p.terminate()
256 256 time.sleep(0.1)
257 257 if p.returncode is not None:
258 258 print("Process is terminated.")
259 259 return
260 260 p.kill()
261 261 print("Process is killed.")
262 262 except OSError:
263 263 pass
264 264 except Exception as e:
265 265 print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e))
266 266 return
267 267 if args.raise_error and p.returncode!=0:
268 268 # If we get here and p.returncode is still None, we must have
269 269 # killed it but not yet seen its return code. We don't wait for it,
270 270 # in case it's stuck in uninterruptible sleep. -9 = SIGKILL
271 271 rc = p.returncode or -9
272 272 raise CalledProcessError(rc, cell)
273 273
274 274 def _run_script(self, p, cell, to_close):
275 275 """callback for running the script in the background"""
276 276 p.stdin.write(cell)
277 277 p.stdin.close()
278 278 for s in to_close:
279 279 s.close()
280 280 p.wait()
281 281
282 282 @line_magic("killbgscripts")
283 283 def killbgscripts(self, _nouse_=''):
284 284 """Kill all BG processes started by %%script and its family."""
285 285 self.kill_bg_processes()
286 286 print("All background processes were killed.")
287 287
288 288 def kill_bg_processes(self):
289 289 """Kill all BG processes which are still running."""
290 290 if not self.bg_processes:
291 291 return
292 292 for p in self.bg_processes:
293 293 if p.returncode is None:
294 294 try:
295 295 p.send_signal(signal.SIGINT)
296 296 except:
297 297 pass
298 298 time.sleep(0.1)
299 299 self._gc_bg_processes()
300 300 if not self.bg_processes:
301 301 return
302 302 for p in self.bg_processes:
303 303 if p.returncode is None:
304 304 try:
305 305 p.terminate()
306 306 except:
307 307 pass
308 308 time.sleep(0.1)
309 309 self._gc_bg_processes()
310 310 if not self.bg_processes:
311 311 return
312 312 for p in self.bg_processes:
313 313 if p.returncode is None:
314 314 try:
315 315 p.kill()
316 316 except:
317 317 pass
318 318 self._gc_bg_processes()
319 319
320 320 def _gc_bg_processes(self):
321 321 self.bg_processes = [p for p in self.bg_processes if p.returncode is None]
@@ -1,128 +1,127 b''
1 1 """ 'editor' hooks for common editors that work well with ipython
2 2
3 3 They should honor the line number argument, at least.
4 4
5 5 Contributions are *very* welcome.
6 6 """
7 7
8 8 import os
9 import pipes
10 9 import shlex
11 10 import subprocess
12 11 import sys
13 12
14 13 from IPython import get_ipython
15 14 from IPython.core.error import TryNext
16 15 from IPython.utils import py3compat
17 16
18 17
19 18 def install_editor(template, wait=False):
20 19 """Installs the editor that is called by IPython for the %edit magic.
21 20
22 21 This overrides the default editor, which is generally set by your EDITOR
23 22 environment variable or is notepad (windows) or vi (linux). By supplying a
24 23 template string `run_template`, you can control how the editor is invoked
25 24 by IPython -- (e.g. the format in which it accepts command line options)
26 25
27 26 Parameters
28 27 ----------
29 28 template : basestring
30 29 run_template acts as a template for how your editor is invoked by
31 30 the shell. It should contain '{filename}', which will be replaced on
32 31 invocation with the file name, and '{line}', $line by line number
33 32 (or 0) to invoke the file with.
34 33 wait : bool
35 34 If `wait` is true, wait until the user presses enter before returning,
36 35 to facilitate non-blocking editors that exit immediately after
37 36 the call.
38 37 """
39 38
40 39 # not all editors support $line, so we'll leave out this check
41 40 # for substitution in ['$file', '$line']:
42 41 # if not substitution in run_template:
43 42 # raise ValueError(('run_template should contain %s'
44 43 # ' for string substitution. You supplied "%s"' % (substitution,
45 44 # run_template)))
46 45
47 46 def call_editor(self, filename, line=0):
48 47 if line is None:
49 48 line = 0
50 cmd = template.format(filename=pipes.quote(filename), line=line)
49 cmd = template.format(filename=shlex.quote(filename), line=line)
51 50 print(">", cmd)
52 # pipes.quote doesn't work right on Windows, but it does after splitting
51 # shlex.quote doesn't work right on Windows, but it does after splitting
53 52 if sys.platform.startswith('win'):
54 53 cmd = shlex.split(cmd)
55 54 proc = subprocess.Popen(cmd, shell=True)
56 55 if proc.wait() != 0:
57 56 raise TryNext()
58 57 if wait:
59 58 py3compat.input("Press Enter when done editing:")
60 59
61 60 get_ipython().set_hook('editor', call_editor)
62 61 get_ipython().editor = template
63 62
64 63
65 64 # in these, exe is always the path/name of the executable. Useful
66 65 # if you don't have the editor directory in your path
67 66 def komodo(exe=u'komodo'):
68 67 """ Activestate Komodo [Edit] """
69 68 install_editor(exe + u' -l {line} {filename}', wait=True)
70 69
71 70
72 71 def scite(exe=u"scite"):
73 72 """ SciTE or Sc1 """
74 73 install_editor(exe + u' {filename} -goto:{line}')
75 74
76 75
77 76 def notepadplusplus(exe=u'notepad++'):
78 77 """ Notepad++ http://notepad-plus.sourceforge.net """
79 78 install_editor(exe + u' -n{line} {filename}')
80 79
81 80
82 81 def jed(exe=u'jed'):
83 82 """ JED, the lightweight emacsish editor """
84 83 install_editor(exe + u' +{line} {filename}')
85 84
86 85
87 86 def idle(exe=u'idle'):
88 87 """ Idle, the editor bundled with python
89 88
90 89 Parameters
91 90 ----------
92 91 exe : str, None
93 92 If none, should be pretty smart about finding the executable.
94 93 """
95 94 if exe is None:
96 95 import idlelib
97 96 p = os.path.dirname(idlelib.__filename__)
98 97 # i'm not sure if this actually works. Is this idle.py script
99 98 # guaranteed to be executable?
100 99 exe = os.path.join(p, 'idle.py')
101 100 install_editor(exe + u' {filename}')
102 101
103 102
104 103 def mate(exe=u'mate'):
105 104 """ TextMate, the missing editor"""
106 105 # wait=True is not required since we're using the -w flag to mate
107 106 install_editor(exe + u' -w -l {line} {filename}')
108 107
109 108
110 109 # ##########################################
111 110 # these are untested, report any problems
112 111 # ##########################################
113 112
114 113
115 114 def emacs(exe=u'emacs'):
116 115 install_editor(exe + u' +{line} {filename}')
117 116
118 117
119 118 def gnuclient(exe=u'gnuclient'):
120 119 install_editor(exe + u' -nw +{line} {filename}')
121 120
122 121
123 122 def crimson_editor(exe=u'cedt.exe'):
124 123 install_editor(exe + u' /L:{line} {filename}')
125 124
126 125
127 126 def kate(exe=u'kate'):
128 127 install_editor(exe + u' -u -l {line} {filename}')
General Comments 0
You need to be logged in to leave comments. Login now