Show More
@@ -0,0 +1,119 b'' | |||||
|
1 | """Common utilities for the various process_* implementations. | |||
|
2 | ||||
|
3 | This file is only meant to be imported by the platform-specific implementations | |||
|
4 | of subprocess utilities, and it contains tools that are common to all of them. | |||
|
5 | """ | |||
|
6 | ||||
|
7 | #----------------------------------------------------------------------------- | |||
|
8 | # Copyright (C) 2010 The IPython Development Team | |||
|
9 | # | |||
|
10 | # Distributed under the terms of the BSD License. The full license is in | |||
|
11 | # the file COPYING, distributed as part of this software. | |||
|
12 | #----------------------------------------------------------------------------- | |||
|
13 | ||||
|
14 | #----------------------------------------------------------------------------- | |||
|
15 | # Imports | |||
|
16 | #----------------------------------------------------------------------------- | |||
|
17 | import subprocess | |||
|
18 | import sys | |||
|
19 | ||||
|
20 | #----------------------------------------------------------------------------- | |||
|
21 | # Function definitions | |||
|
22 | #----------------------------------------------------------------------------- | |||
|
23 | ||||
|
24 | def read_no_interrupt(p): | |||
|
25 | """Read from a pipe ignoring EINTR errors. | |||
|
26 | ||||
|
27 | This is necessary because when reading from pipes with GUI event loops | |||
|
28 | running in the background, often interrupts are raised that stop the | |||
|
29 | command from completing.""" | |||
|
30 | import errno | |||
|
31 | ||||
|
32 | try: | |||
|
33 | return p.read() | |||
|
34 | except IOError, err: | |||
|
35 | if err.errno != errno.EINTR: | |||
|
36 | raise | |||
|
37 | ||||
|
38 | ||||
|
39 | def process_handler(cmd, callback, stderr=subprocess.PIPE): | |||
|
40 | """Open a command in a shell subprocess and execute a callback. | |||
|
41 | ||||
|
42 | This function provides common scaffolding for creating subprocess.Popen() | |||
|
43 | calls. It creates a Popen object and then calls the callback with it. | |||
|
44 | ||||
|
45 | Parameters | |||
|
46 | ---------- | |||
|
47 | cmd : str | |||
|
48 | A string to be executed with the underlying system shell (by calling | |||
|
49 | :func:`Popen` with ``shell=True``. | |||
|
50 | ||||
|
51 | callback : callable | |||
|
52 | A one-argument function that will be called with the Popen object. | |||
|
53 | ||||
|
54 | stderr : file descriptor number, optional | |||
|
55 | By default this is set to ``subprocess.PIPE``, but you can also pass the | |||
|
56 | value ``subprocess.STDOUT`` to force the subprocess' stderr to go into | |||
|
57 | the same file descriptor as its stdout. This is useful to read stdout | |||
|
58 | and stderr combined in the order they are generated. | |||
|
59 | ||||
|
60 | Returns | |||
|
61 | ------- | |||
|
62 | The return value of the provided callback is returned. | |||
|
63 | """ | |||
|
64 | sys.stdout.flush() | |||
|
65 | sys.stderr.flush() | |||
|
66 | p = subprocess.Popen(cmd, shell=True, | |||
|
67 | stdin=subprocess.PIPE, | |||
|
68 | stdout=subprocess.PIPE, | |||
|
69 | stderr=stderr, | |||
|
70 | close_fds=True) | |||
|
71 | ||||
|
72 | try: | |||
|
73 | out = callback(p) | |||
|
74 | except KeyboardInterrupt: | |||
|
75 | print('^C') | |||
|
76 | sys.stdout.flush() | |||
|
77 | sys.stderr.flush() | |||
|
78 | out = None | |||
|
79 | finally: | |||
|
80 | # Make really sure that we don't leave processes behind, in case the | |||
|
81 | # call above raises an exception | |||
|
82 | # We start by assuming the subprocess finished (to avoid NameErrors | |||
|
83 | # later depending on the path taken) | |||
|
84 | if p.returncode is None: | |||
|
85 | try: | |||
|
86 | p.terminate() | |||
|
87 | p.poll() | |||
|
88 | except OSError: | |||
|
89 | pass | |||
|
90 | # One last try on our way out | |||
|
91 | if p.returncode is None: | |||
|
92 | try: | |||
|
93 | p.kill() | |||
|
94 | except OSError: | |||
|
95 | pass | |||
|
96 | ||||
|
97 | return out | |||
|
98 | ||||
|
99 | ||||
|
100 | def getoutputerror(cmd): | |||
|
101 | """Return (standard output, standard error) of executing cmd in a shell. | |||
|
102 | ||||
|
103 | Accepts the same arguments as os.system(). | |||
|
104 | ||||
|
105 | Parameters | |||
|
106 | ---------- | |||
|
107 | cmd : str | |||
|
108 | A command to be executed in the system shell. | |||
|
109 | ||||
|
110 | Returns | |||
|
111 | ------- | |||
|
112 | stdout : str | |||
|
113 | stderr : str | |||
|
114 | """ | |||
|
115 | ||||
|
116 | out_err = process_handler(cmd, lambda p: p.communicate()) | |||
|
117 | if out_err is None: | |||
|
118 | out_err = '', '' | |||
|
119 | return out_err |
@@ -0,0 +1,169 b'' | |||||
|
1 | """Posix-specific implementation of process utilities. | |||
|
2 | ||||
|
3 | This file is only meant to be imported by process.py, not by end-users. | |||
|
4 | """ | |||
|
5 | ||||
|
6 | #----------------------------------------------------------------------------- | |||
|
7 | # Copyright (C) 2010 The IPython Development Team | |||
|
8 | # | |||
|
9 | # Distributed under the terms of the BSD License. The full license is in | |||
|
10 | # the file COPYING, distributed as part of this software. | |||
|
11 | #----------------------------------------------------------------------------- | |||
|
12 | ||||
|
13 | #----------------------------------------------------------------------------- | |||
|
14 | # Imports | |||
|
15 | #----------------------------------------------------------------------------- | |||
|
16 | from __future__ import print_function | |||
|
17 | ||||
|
18 | # Stdlib | |||
|
19 | import subprocess as sp | |||
|
20 | import sys | |||
|
21 | ||||
|
22 | # Third-party | |||
|
23 | # We ship our own copy of pexpect (it's a single file) to minimize dependencies | |||
|
24 | # for users, but it's only used if we don't find the system copy. | |||
|
25 | try: | |||
|
26 | import pexpect | |||
|
27 | except ImportError: | |||
|
28 | from IPython.external import pexpect | |||
|
29 | ||||
|
30 | # Our own | |||
|
31 | from .autoattr import auto_attr | |||
|
32 | ||||
|
33 | #----------------------------------------------------------------------------- | |||
|
34 | # Function definitions | |||
|
35 | #----------------------------------------------------------------------------- | |||
|
36 | ||||
|
37 | def _find_cmd(cmd): | |||
|
38 | """Find the full path to a command using which.""" | |||
|
39 | ||||
|
40 | return sp.Popen(['/usr/bin/env', 'which', cmd], | |||
|
41 | stdout=sp.PIPE).communicate()[0] | |||
|
42 | ||||
|
43 | ||||
|
44 | class ProcessHandler(object): | |||
|
45 | """Execute subprocesses under the control of pexpect. | |||
|
46 | """ | |||
|
47 | # Timeout in seconds to wait on each reading of the subprocess' output. | |||
|
48 | # This should not be set too low to avoid cpu overusage from our side, | |||
|
49 | # since we read in a loop whose period is controlled by this timeout. | |||
|
50 | read_timeout = 0.05 | |||
|
51 | ||||
|
52 | # Timeout to give a process if we receive SIGINT, between sending the | |||
|
53 | # SIGINT to the process and forcefully terminating it. | |||
|
54 | terminate_timeout = 0.2 | |||
|
55 | ||||
|
56 | # File object where stdout and stderr of the subprocess will be written | |||
|
57 | logfile = None | |||
|
58 | ||||
|
59 | # Shell to call for subprocesses to execute | |||
|
60 | sh = None | |||
|
61 | ||||
|
62 | @auto_attr | |||
|
63 | def sh(self): | |||
|
64 | sh = pexpect.which('sh') | |||
|
65 | if sh is None: | |||
|
66 | raise OSError('"sh" shell not found') | |||
|
67 | return sh | |||
|
68 | ||||
|
69 | def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None): | |||
|
70 | """Arguments are used for pexpect calls.""" | |||
|
71 | self.read_timeout = (ProcessHandler.read_timeout if read_timeout is | |||
|
72 | None else read_timeout) | |||
|
73 | self.terminate_timeout = (ProcessHandler.terminate_timeout if | |||
|
74 | terminate_timeout is None else | |||
|
75 | terminate_timeout) | |||
|
76 | self.logfile = sys.stdout if logfile is None else logfile | |||
|
77 | ||||
|
78 | def getoutput(self, cmd): | |||
|
79 | """Run a command and return its stdout/stderr as a string. | |||
|
80 | ||||
|
81 | Parameters | |||
|
82 | ---------- | |||
|
83 | cmd : str | |||
|
84 | A command to be executed in the system shell. | |||
|
85 | ||||
|
86 | Returns | |||
|
87 | ------- | |||
|
88 | output : str | |||
|
89 | A string containing the combination of stdout and stderr from the | |||
|
90 | subprocess, in whatever order the subprocess originally wrote to its | |||
|
91 | file descriptors (so the order of the information in this string is the | |||
|
92 | correct order as would be seen if running the command in a terminal). | |||
|
93 | """ | |||
|
94 | pcmd = self._make_cmd(cmd) | |||
|
95 | try: | |||
|
96 | return pexpect.run(pcmd).replace('\r\n', '\n') | |||
|
97 | except KeyboardInterrupt: | |||
|
98 | print('^C', file=sys.stderr, end='') | |||
|
99 | ||||
|
100 | def system(self, cmd): | |||
|
101 | """Execute a command in a subshell. | |||
|
102 | ||||
|
103 | Parameters | |||
|
104 | ---------- | |||
|
105 | cmd : str | |||
|
106 | A command to be executed in the system shell. | |||
|
107 | ||||
|
108 | Returns | |||
|
109 | ------- | |||
|
110 | None : we explicitly do NOT return the subprocess status code, as this | |||
|
111 | utility is meant to be used extensively in IPython, where any return | |||
|
112 | value would trigger :func:`sys.displayhook` calls. | |||
|
113 | """ | |||
|
114 | pcmd = self._make_cmd(cmd) | |||
|
115 | # Patterns to match on the output, for pexpect. We read input and | |||
|
116 | # allow either a short timeout or EOF | |||
|
117 | patterns = [pexpect.TIMEOUT, pexpect.EOF] | |||
|
118 | # the index of the EOF pattern in the list. | |||
|
119 | EOF_index = 1 # Fix this index if you change the list!! | |||
|
120 | # The size of the output stored so far in the process output buffer. | |||
|
121 | # Since pexpect only appends to this buffer, each time we print we | |||
|
122 | # record how far we've printed, so that next time we only print *new* | |||
|
123 | # content from the buffer. | |||
|
124 | out_size = 0 | |||
|
125 | try: | |||
|
126 | # Since we're not really searching the buffer for text patterns, we | |||
|
127 | # can set pexpect's search window to be tiny and it won't matter. | |||
|
128 | # We only search for the 'patterns' timeout or EOF, which aren't in | |||
|
129 | # the text itself. | |||
|
130 | child = pexpect.spawn(pcmd, searchwindowsize=1) | |||
|
131 | flush = sys.stdout.flush | |||
|
132 | while True: | |||
|
133 | # res is the index of the pattern that caused the match, so we | |||
|
134 | # know whether we've finished (if we matched EOF) or not | |||
|
135 | res_idx = child.expect_list(patterns, self.read_timeout) | |||
|
136 | print(child.before[out_size:], end='') | |||
|
137 | flush() | |||
|
138 | # Update the pointer to what we've already printed | |||
|
139 | out_size = len(child.before) | |||
|
140 | if res_idx==EOF_index: | |||
|
141 | break | |||
|
142 | except KeyboardInterrupt: | |||
|
143 | # We need to send ^C to the process. The ascii code for '^C' is 3 | |||
|
144 | # (the character is known as ETX for 'End of Text', see | |||
|
145 | # curses.ascii.ETX). | |||
|
146 | child.sendline(chr(3)) | |||
|
147 | # Read and print any more output the program might produce on its | |||
|
148 | # way out. | |||
|
149 | try: | |||
|
150 | out_size = len(child.before) | |||
|
151 | child.expect_list(patterns, self.terminate_timeout) | |||
|
152 | print(child.before[out_size:], end='') | |||
|
153 | except KeyboardInterrupt: | |||
|
154 | # Impatient users tend to type it multiple times | |||
|
155 | pass | |||
|
156 | finally: | |||
|
157 | # Ensure the subprocess really is terminated | |||
|
158 | child.terminate(force=True) | |||
|
159 | ||||
|
160 | def _make_cmd(self, cmd): | |||
|
161 | return '%s -c "%s"' % (self.sh, cmd) | |||
|
162 | ||||
|
163 | ||||
|
164 | ||||
|
165 | # Make objects with a functional interface for outside use | |||
|
166 | __ph = ProcessHandler() | |||
|
167 | ||||
|
168 | system = __ph.system | |||
|
169 | getoutput = __ph.getoutput |
@@ -0,0 +1,137 b'' | |||||
|
1 | """Windows-specific implementation of process utilities. | |||
|
2 | ||||
|
3 | This file is only meant to be imported by process.py, not by end-users. | |||
|
4 | """ | |||
|
5 | ||||
|
6 | #----------------------------------------------------------------------------- | |||
|
7 | # Copyright (C) 2010 The IPython Development Team | |||
|
8 | # | |||
|
9 | # Distributed under the terms of the BSD License. The full license is in | |||
|
10 | # the file COPYING, distributed as part of this software. | |||
|
11 | #----------------------------------------------------------------------------- | |||
|
12 | ||||
|
13 | #----------------------------------------------------------------------------- | |||
|
14 | # Imports | |||
|
15 | #----------------------------------------------------------------------------- | |||
|
16 | from __future__ import print_function | |||
|
17 | ||||
|
18 | # stdlib | |||
|
19 | import os | |||
|
20 | import sys | |||
|
21 | ||||
|
22 | from subprocess import STDOUT | |||
|
23 | ||||
|
24 | # our own imports | |||
|
25 | from ._process_common import read_no_interrupt, process_handler | |||
|
26 | ||||
|
27 | #----------------------------------------------------------------------------- | |||
|
28 | # Function definitions | |||
|
29 | #----------------------------------------------------------------------------- | |||
|
30 | ||||
|
31 | class AvoidUNCPath(object): | |||
|
32 | """A context manager to protect command execution from UNC paths. | |||
|
33 | ||||
|
34 | In the Win32 API, commands can't be invoked with the cwd being a UNC path. | |||
|
35 | This context manager temporarily changes directory to the 'C:' drive on | |||
|
36 | entering, and restores the original working directory on exit. | |||
|
37 | ||||
|
38 | The context manager returns the starting working directory *if* it made a | |||
|
39 | change and None otherwise, so that users can apply the necessary adjustment | |||
|
40 | to their system calls in the event of a change. | |||
|
41 | ||||
|
42 | Example | |||
|
43 | ------- | |||
|
44 | :: | |||
|
45 | cmd = 'dir' | |||
|
46 | with AvoidUNCPath() as path: | |||
|
47 | if path is not None: | |||
|
48 | cmd = '"pushd %s &&"%s' % (path, cmd) | |||
|
49 | os.system(cmd) | |||
|
50 | """ | |||
|
51 | def __enter__(self): | |||
|
52 | self.path = os.getcwd() | |||
|
53 | self.is_unc_path = self.path.startswith(r"\\") | |||
|
54 | if self.is_unc_path: | |||
|
55 | # change to c drive (as cmd.exe cannot handle UNC addresses) | |||
|
56 | os.chdir("C:") | |||
|
57 | return self.path | |||
|
58 | ||||
|
59 | def __exit__(self, exc_type, exc_value, traceback): | |||
|
60 | if self.is_unc_path: | |||
|
61 | os.chdir(self.path) | |||
|
62 | ||||
|
63 | ||||
|
64 | def _find_cmd(cmd): | |||
|
65 | """Find the full path to a .bat or .exe using the win32api module.""" | |||
|
66 | try: | |||
|
67 | from win32api import SearchPath | |||
|
68 | except ImportError: | |||
|
69 | raise ImportError('you need to have pywin32 installed for this to work') | |||
|
70 | else: | |||
|
71 | PATH = os.environ['PATH'] | |||
|
72 | extensions = ['.exe', '.com', '.bat', '.py'] | |||
|
73 | path = None | |||
|
74 | for ext in extensions: | |||
|
75 | try: | |||
|
76 | path = SearchPath(PATH, cmd + ext)[0] | |||
|
77 | except: | |||
|
78 | pass | |||
|
79 | if path is None: | |||
|
80 | raise OSError("command %r not found" % cmd) | |||
|
81 | else: | |||
|
82 | return path | |||
|
83 | ||||
|
84 | ||||
|
85 | def _system_body(p): | |||
|
86 | """Callback for _system.""" | |||
|
87 | for line in read_no_interrupt(p.stdout).splitlines(): | |||
|
88 | print(line, file=sys.stdout) | |||
|
89 | for line in read_no_interrupt(p.stderr).splitlines(): | |||
|
90 | print(line, file=sys.stderr) | |||
|
91 | ||||
|
92 | ||||
|
93 | def system(cmd): | |||
|
94 | """Win32 version of os.system() that works with network shares. | |||
|
95 | ||||
|
96 | Note that this implementation returns None, as meant for use in IPython. | |||
|
97 | ||||
|
98 | Parameters | |||
|
99 | ---------- | |||
|
100 | cmd : str | |||
|
101 | A command to be executed in the system shell. | |||
|
102 | ||||
|
103 | Returns | |||
|
104 | ------- | |||
|
105 | None : we explicitly do NOT return the subprocess status code, as this | |||
|
106 | utility is meant to be used extensively in IPython, where any return value | |||
|
107 | would trigger :func:`sys.displayhook` calls. | |||
|
108 | """ | |||
|
109 | with AvoidUNCPath() as path: | |||
|
110 | if path is not None: | |||
|
111 | cmd = '"pushd %s &&"%s' % (path, cmd) | |||
|
112 | process_handler(cmd, _system_body) | |||
|
113 | ||||
|
114 | ||||
|
115 | def getoutput(cmd): | |||
|
116 | """Return standard output of executing cmd in a shell. | |||
|
117 | ||||
|
118 | Accepts the same arguments as os.system(). | |||
|
119 | ||||
|
120 | Parameters | |||
|
121 | ---------- | |||
|
122 | cmd : str | |||
|
123 | A command to be executed in the system shell. | |||
|
124 | ||||
|
125 | Returns | |||
|
126 | ------- | |||
|
127 | stdout : str | |||
|
128 | """ | |||
|
129 | ||||
|
130 | with AvoidUNCPath() as path: | |||
|
131 | if path is not None: | |||
|
132 | cmd = '"pushd %s &&"%s' % (path, cmd) | |||
|
133 | out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT) | |||
|
134 | ||||
|
135 | if out is None: | |||
|
136 | out = '' | |||
|
137 | return out |
@@ -57,7 +57,7 b' from IPython.utils.doctestreload import doctest_reload' | |||||
57 | from IPython.utils.io import ask_yes_no, rprint |
|
57 | from IPython.utils.io import ask_yes_no, rprint | |
58 | from IPython.utils.ipstruct import Struct |
|
58 | from IPython.utils.ipstruct import Struct | |
59 | from IPython.utils.path import get_home_dir, get_ipython_dir, HomeDirError |
|
59 | from IPython.utils.path import get_home_dir, get_ipython_dir, HomeDirError | |
60 |
from IPython.utils.process import system, getoutput |
|
60 | from IPython.utils.process import system, getoutput | |
61 | from IPython.utils.strdispatch import StrDispatch |
|
61 | from IPython.utils.strdispatch import StrDispatch | |
62 | from IPython.utils.syspathcontext import prepended_to_syspath |
|
62 | from IPython.utils.syspathcontext import prepended_to_syspath | |
63 | from IPython.utils.text import num_ini_spaces |
|
63 | from IPython.utils.text import num_ini_spaces | |
@@ -1667,12 +1667,6 b' class InteractiveShell(Configurable, Magic):' | |||||
1667 | raise OSError("Background processes not supported.") |
|
1667 | raise OSError("Background processes not supported.") | |
1668 | return getoutput(self.var_expand(cmd, depth=2)) |
|
1668 | return getoutput(self.var_expand(cmd, depth=2)) | |
1669 |
|
1669 | |||
1670 | def getoutputerror(self, cmd): |
|
|||
1671 | """Get stdout and stderr from a subprocess.""" |
|
|||
1672 | if cmd.endswith('&'): |
|
|||
1673 | raise OSError("Background processes not supported.") |
|
|||
1674 | return getoutputerror(self.var_expand(cmd, depth=2)) |
|
|||
1675 |
|
||||
1676 | #------------------------------------------------------------------------- |
|
1670 | #------------------------------------------------------------------------- | |
1677 | # Things related to aliases |
|
1671 | # Things related to aliases | |
1678 | #------------------------------------------------------------------------- |
|
1672 | #------------------------------------------------------------------------- |
@@ -3072,9 +3072,7 b' Defaulting color scheme to \'NoColor\'"""' | |||||
3072 | except ValueError: |
|
3072 | except ValueError: | |
3073 | var,cmd = '','' |
|
3073 | var,cmd = '','' | |
3074 | # If all looks ok, proceed |
|
3074 | # If all looks ok, proceed | |
3075 |
out |
|
3075 | out = self.shell.getoutput(cmd) | |
3076 | if err: |
|
|||
3077 | print >> IPython.utils.io.Term.cerr, err |
|
|||
3078 | if opts.has_key('l'): |
|
3076 | if opts.has_key('l'): | |
3079 | out = SList(out.split('\n')) |
|
3077 | out = SList(out.split('\n')) | |
3080 | else: |
|
3078 | else: | |
@@ -3122,10 +3120,9 b' Defaulting color scheme to \'NoColor\'"""' | |||||
3122 | system commands.""" |
|
3120 | system commands.""" | |
3123 |
|
3121 | |||
3124 | if parameter_s: |
|
3122 | if parameter_s: | |
3125 |
out |
|
3123 | out = self.shell.getoutput(parameter_s) | |
3126 |
if e |
|
3124 | if out is not None: | |
3127 | print >> IPython.utils.io.Term.cerr, err |
|
3125 | return SList(out.splitlines()) | |
3128 | return SList(out.split('\n')) |
|
|||
3129 |
|
3126 | |||
3130 | def magic_r(self, parameter_s=''): |
|
3127 | def magic_r(self, parameter_s=''): | |
3131 | """Repeat previous input. |
|
3128 | """Repeat previous input. |
@@ -37,7 +37,7 b' from IPython.core.error import TryNext' | |||||
37 | from IPython.utils.cursesimport import use_curses |
|
37 | from IPython.utils.cursesimport import use_curses | |
38 | from IPython.utils.data import chop |
|
38 | from IPython.utils.data import chop | |
39 | import IPython.utils.io |
|
39 | import IPython.utils.io | |
40 |
from IPython.utils.process import |
|
40 | from IPython.utils.process import system | |
41 | from IPython.utils.terminal import get_terminal_size |
|
41 | from IPython.utils.terminal import get_terminal_size | |
42 |
|
42 | |||
43 |
|
43 | |||
@@ -210,7 +210,7 b' def page_file(fname, start=0, pager_cmd=None):' | |||||
210 | try: |
|
210 | try: | |
211 | if os.environ['TERM'] in ['emacs','dumb']: |
|
211 | if os.environ['TERM'] in ['emacs','dumb']: | |
212 | raise EnvironmentError |
|
212 | raise EnvironmentError | |
213 |
|
|
213 | system(pager_cmd + ' ' + fname) | |
214 | except: |
|
214 | except: | |
215 | try: |
|
215 | try: | |
216 | if start > 0: |
|
216 | if start > 0: |
@@ -1,6 +1,4 b'' | |||||
1 | #!/usr/bin/env python |
|
1 | """Descriptor utilities. | |
2 | # encoding: utf-8 |
|
|||
3 | """Descriptor support for NIPY. |
|
|||
4 |
|
2 | |||
5 | Utilities to support special Python descriptors [1,2], in particular the use of |
|
3 | Utilities to support special Python descriptors [1,2], in particular the use of | |
6 | a useful pattern for properties we call 'one time properties'. These are |
|
4 | a useful pattern for properties we call 'one time properties'. These are | |
@@ -24,6 +22,10 b' Notes' | |||||
24 | ----- |
|
22 | ----- | |
25 | This module is taken from the NiPy project |
|
23 | This module is taken from the NiPy project | |
26 | (http://neuroimaging.scipy.org/site/index.html), and is BSD licensed. |
|
24 | (http://neuroimaging.scipy.org/site/index.html), and is BSD licensed. | |
|
25 | ||||
|
26 | Authors | |||
|
27 | ------- | |||
|
28 | - Fernando Perez. | |||
27 | """ |
|
29 | """ | |
28 |
|
30 | |||
29 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- |
@@ -18,7 +18,7 b' import os' | |||||
18 | import sys |
|
18 | import sys | |
19 |
|
19 | |||
20 | import IPython |
|
20 | import IPython | |
21 |
from IPython.utils.process import |
|
21 | from IPython.utils.process import system | |
22 | from IPython.utils.importstring import import_item |
|
22 | from IPython.utils.importstring import import_item | |
23 |
|
23 | |||
24 | #----------------------------------------------------------------------------- |
|
24 | #----------------------------------------------------------------------------- | |
@@ -341,5 +341,5 b' def target_update(target,deps,cmd):' | |||||
341 | command if target is outdated.""" |
|
341 | command if target is outdated.""" | |
342 |
|
342 | |||
343 | if target_outdated(target,deps): |
|
343 | if target_outdated(target,deps): | |
344 |
|
|
344 | system(cmd) | |
345 |
|
345 |
@@ -13,13 +13,20 b' Utilities for working with external processes.' | |||||
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 | # Imports |
|
14 | # Imports | |
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
|
16 | from __future__ import print_function | |||
16 |
|
17 | |||
|
18 | # Stdlib | |||
17 | import os |
|
19 | import os | |
18 | import sys |
|
20 | import sys | |
19 | import shlex |
|
21 | import shlex | |
20 | import subprocess |
|
|||
21 |
|
22 | |||
22 | from IPython.utils.terminal import set_term_title |
|
23 | # Our own | |
|
24 | if sys.platform == 'win32': | |||
|
25 | from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath | |||
|
26 | else: | |||
|
27 | from ._process_posix import _find_cmd, system, getoutput | |||
|
28 | ||||
|
29 | from ._process_common import getoutputerror | |||
23 |
|
30 | |||
24 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
25 | # Code |
|
32 | # Code | |
@@ -30,39 +37,6 b' class FindCmdError(Exception):' | |||||
30 | pass |
|
37 | pass | |
31 |
|
38 | |||
32 |
|
39 | |||
33 | def _find_cmd(cmd): |
|
|||
34 | """Find the full path to a command using which.""" |
|
|||
35 | return os.popen('which %s' % cmd).read().strip() |
|
|||
36 |
|
||||
37 |
|
||||
38 | if os.name == 'posix': |
|
|||
39 | def _find_cmd(cmd): |
|
|||
40 | """Find the full path to a command using which.""" |
|
|||
41 | return getoutputerror('/usr/bin/env which %s' % cmd)[0] |
|
|||
42 |
|
||||
43 |
|
||||
44 | if sys.platform == 'win32': |
|
|||
45 | def _find_cmd(cmd): |
|
|||
46 | """Find the full path to a .bat or .exe using the win32api module.""" |
|
|||
47 | try: |
|
|||
48 | from win32api import SearchPath |
|
|||
49 | except ImportError: |
|
|||
50 | raise ImportError('you need to have pywin32 installed for this to work') |
|
|||
51 | else: |
|
|||
52 | PATH = os.environ['PATH'] |
|
|||
53 | extensions = ['.exe', '.com', '.bat', '.py'] |
|
|||
54 | path = None |
|
|||
55 | for ext in extensions: |
|
|||
56 | try: |
|
|||
57 | path = SearchPath(PATH,cmd + ext)[0] |
|
|||
58 | except: |
|
|||
59 | pass |
|
|||
60 | if path is None: |
|
|||
61 | raise OSError("command %r not found" % cmd) |
|
|||
62 | else: |
|
|||
63 | return path |
|
|||
64 |
|
||||
65 |
|
||||
66 | def find_cmd(cmd): |
|
40 | def find_cmd(cmd): | |
67 | """Find absolute path to executable cmd in a cross platform manner. |
|
41 | """Find absolute path to executable cmd in a cross platform manner. | |
68 |
|
42 | |||
@@ -87,7 +61,7 b' def find_cmd(cmd):' | |||||
87 | if cmd == 'python': |
|
61 | if cmd == 'python': | |
88 | return os.path.abspath(sys.executable) |
|
62 | return os.path.abspath(sys.executable) | |
89 | try: |
|
63 | try: | |
90 | path = _find_cmd(cmd) |
|
64 | path = _find_cmd(cmd).rstrip() | |
91 | except OSError: |
|
65 | except OSError: | |
92 | raise FindCmdError('command could not be found: %s' % cmd) |
|
66 | raise FindCmdError('command could not be found: %s' % cmd) | |
93 | # which returns empty if not found |
|
67 | # which returns empty if not found | |
@@ -147,28 +121,6 b' def arg_split(s, posix=False):' | |||||
147 | return list(lex) |
|
121 | return list(lex) | |
148 |
|
122 | |||
149 |
|
123 | |||
150 | def system(cmd, verbose=0, debug=0, header=''): |
|
|||
151 | """Execute a system command, return its exit status. |
|
|||
152 |
|
||||
153 | Options: |
|
|||
154 |
|
||||
155 | - verbose (0): print the command to be executed. |
|
|||
156 |
|
||||
157 | - debug (0): only print, do not actually execute. |
|
|||
158 |
|
||||
159 | - header (''): Header to print on screen prior to the executed command (it |
|
|||
160 | is only prepended to the command, no newlines are added). |
|
|||
161 |
|
||||
162 | Note: a stateful version of this function is available through the |
|
|||
163 | SystemExec class.""" |
|
|||
164 |
|
||||
165 | stat = 0 |
|
|||
166 | if verbose or debug: print header+cmd |
|
|||
167 | sys.stdout.flush() |
|
|||
168 | if not debug: stat = os.system(cmd) |
|
|||
169 | return stat |
|
|||
170 |
|
||||
171 |
|
||||
172 | def abbrev_cwd(): |
|
124 | def abbrev_cwd(): | |
173 | """ Return abbreviated version of cwd, e.g. d:mydir """ |
|
125 | """ Return abbreviated version of cwd, e.g. d:mydir """ | |
174 | cwd = os.getcwd().replace('\\','/') |
|
126 | cwd = os.getcwd().replace('\\','/') | |
@@ -186,182 +138,3 b' def abbrev_cwd():' | |||||
186 |
|
138 | |||
187 | return (drivepart + ( |
|
139 | return (drivepart + ( | |
188 | cwd == '/' and '/' or tail)) |
|
140 | cwd == '/' and '/' or tail)) | |
189 |
|
||||
190 |
|
||||
191 | # This function is used by ipython in a lot of places to make system calls. |
|
|||
192 | # We need it to be slightly different under win32, due to the vagaries of |
|
|||
193 | # 'network shares'. A win32 override is below. |
|
|||
194 |
|
||||
195 | def shell(cmd, verbose=0, debug=0, header=''): |
|
|||
196 | """Execute a command in the system shell, always return None. |
|
|||
197 |
|
||||
198 | Options: |
|
|||
199 |
|
||||
200 | - verbose (0): print the command to be executed. |
|
|||
201 |
|
||||
202 | - debug (0): only print, do not actually execute. |
|
|||
203 |
|
||||
204 | - header (''): Header to print on screen prior to the executed command (it |
|
|||
205 | is only prepended to the command, no newlines are added). |
|
|||
206 |
|
||||
207 | Note: this is similar to system(), but it returns None so it can |
|
|||
208 | be conveniently used in interactive loops without getting the return value |
|
|||
209 | (typically 0) printed many times.""" |
|
|||
210 |
|
||||
211 | stat = 0 |
|
|||
212 | if verbose or debug: print header+cmd |
|
|||
213 | # flush stdout so we don't mangle python's buffering |
|
|||
214 | sys.stdout.flush() |
|
|||
215 |
|
||||
216 | if not debug: |
|
|||
217 | set_term_title("IPy " + cmd) |
|
|||
218 | os.system(cmd) |
|
|||
219 | set_term_title("IPy " + abbrev_cwd()) |
|
|||
220 |
|
||||
221 | # override shell() for win32 to deal with network shares |
|
|||
222 | if os.name in ('nt','dos'): |
|
|||
223 |
|
||||
224 | shell_ori = shell |
|
|||
225 |
|
||||
226 | def shell(cmd, verbose=0, debug=0, header=''): |
|
|||
227 | if os.getcwd().startswith(r"\\"): |
|
|||
228 | path = os.getcwd() |
|
|||
229 | # change to c drive (cannot be on UNC-share when issuing os.system, |
|
|||
230 | # as cmd.exe cannot handle UNC addresses) |
|
|||
231 | os.chdir("c:") |
|
|||
232 | # issue pushd to the UNC-share and then run the command |
|
|||
233 | try: |
|
|||
234 | shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header) |
|
|||
235 | finally: |
|
|||
236 | os.chdir(path) |
|
|||
237 | else: |
|
|||
238 | shell_ori(cmd,verbose,debug,header) |
|
|||
239 |
|
||||
240 | shell.__doc__ = shell_ori.__doc__ |
|
|||
241 |
|
||||
242 |
|
||||
243 | def getoutput(cmd, verbose=0, debug=0, header='', split=0): |
|
|||
244 | """Dummy substitute for perl's backquotes. |
|
|||
245 |
|
||||
246 | Executes a command and returns the output. |
|
|||
247 |
|
||||
248 | Accepts the same arguments as system(), plus: |
|
|||
249 |
|
||||
250 | - split(0): if true, the output is returned as a list split on newlines. |
|
|||
251 |
|
||||
252 | Note: a stateful version of this function is available through the |
|
|||
253 | SystemExec class. |
|
|||
254 |
|
||||
255 | This is pretty much deprecated and rarely used, getoutputerror may be |
|
|||
256 | what you need. |
|
|||
257 |
|
||||
258 | """ |
|
|||
259 |
|
||||
260 | if verbose or debug: print header+cmd |
|
|||
261 | if not debug: |
|
|||
262 | pipe = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout |
|
|||
263 | output = pipe.read() |
|
|||
264 | # stipping last \n is here for backwards compat. |
|
|||
265 | if output.endswith('\n'): |
|
|||
266 | output = output[:-1] |
|
|||
267 | if split: |
|
|||
268 | return output.split('\n') |
|
|||
269 | else: |
|
|||
270 | return output |
|
|||
271 |
|
||||
272 |
|
||||
273 | # for compatibility with older naming conventions |
|
|||
274 | xsys = system |
|
|||
275 |
|
||||
276 |
|
||||
277 | def getoutputerror(cmd, verbose=0, debug=0, header='', split=0): |
|
|||
278 | """Return (standard output,standard error) of executing cmd in a shell. |
|
|||
279 |
|
||||
280 | Accepts the same arguments as system(), plus: |
|
|||
281 |
|
||||
282 | - split(0): if true, each of stdout/err is returned as a list split on |
|
|||
283 | newlines. |
|
|||
284 |
|
||||
285 | Note: a stateful version of this function is available through the |
|
|||
286 | SystemExec class.""" |
|
|||
287 |
|
||||
288 | if verbose or debug: print header+cmd |
|
|||
289 | if not cmd: |
|
|||
290 | if split: |
|
|||
291 | return [],[] |
|
|||
292 | else: |
|
|||
293 | return '','' |
|
|||
294 | if not debug: |
|
|||
295 | p = subprocess.Popen(cmd, shell=True, |
|
|||
296 | stdin=subprocess.PIPE, |
|
|||
297 | stdout=subprocess.PIPE, |
|
|||
298 | stderr=subprocess.PIPE, |
|
|||
299 | close_fds=True) |
|
|||
300 | pin, pout, perr = (p.stdin, p.stdout, p.stderr) |
|
|||
301 |
|
||||
302 | tout = pout.read().rstrip() |
|
|||
303 | terr = perr.read().rstrip() |
|
|||
304 | pin.close() |
|
|||
305 | pout.close() |
|
|||
306 | perr.close() |
|
|||
307 | if split: |
|
|||
308 | return tout.split('\n'),terr.split('\n') |
|
|||
309 | else: |
|
|||
310 | return tout,terr |
|
|||
311 |
|
||||
312 |
|
||||
313 | class SystemExec: |
|
|||
314 | """Access the system and getoutput functions through a stateful interface. |
|
|||
315 |
|
||||
316 | Note: here we refer to the system and getoutput functions from this |
|
|||
317 | library, not the ones from the standard python library. |
|
|||
318 |
|
||||
319 | This class offers the system and getoutput functions as methods, but the |
|
|||
320 | verbose, debug and header parameters can be set for the instance (at |
|
|||
321 | creation time or later) so that they don't need to be specified on each |
|
|||
322 | call. |
|
|||
323 |
|
||||
324 | For efficiency reasons, there's no way to override the parameters on a |
|
|||
325 | per-call basis other than by setting instance attributes. If you need |
|
|||
326 | local overrides, it's best to directly call system() or getoutput(). |
|
|||
327 |
|
||||
328 | The following names are provided as alternate options: |
|
|||
329 | - xsys: alias to system |
|
|||
330 | - bq: alias to getoutput |
|
|||
331 |
|
||||
332 | An instance can then be created as: |
|
|||
333 | >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ') |
|
|||
334 | """ |
|
|||
335 |
|
||||
336 | def __init__(self, verbose=0, debug=0, header='', split=0): |
|
|||
337 | """Specify the instance's values for verbose, debug and header.""" |
|
|||
338 | self.verbose = verbose |
|
|||
339 | self.debug = debug |
|
|||
340 | self.header = header |
|
|||
341 | self.split = split |
|
|||
342 |
|
||||
343 | def system(self, cmd): |
|
|||
344 | """Stateful interface to system(), with the same keyword parameters.""" |
|
|||
345 |
|
||||
346 | system(cmd, self.verbose, self.debug, self.header) |
|
|||
347 |
|
||||
348 | def shell(self, cmd): |
|
|||
349 | """Stateful interface to shell(), with the same keyword parameters.""" |
|
|||
350 |
|
||||
351 | shell(cmd, self.verbose, self.debug, self.header) |
|
|||
352 |
|
||||
353 | xsys = system # alias |
|
|||
354 |
|
||||
355 | def getoutput(self, cmd): |
|
|||
356 | """Stateful interface to getoutput().""" |
|
|||
357 |
|
||||
358 | return getoutput(cmd, self.verbose, self.debug, self.header, self.split) |
|
|||
359 |
|
||||
360 | def getoutputerror(self, cmd): |
|
|||
361 | """Stateful interface to getoutputerror().""" |
|
|||
362 |
|
||||
363 | return getoutputerror(cmd, self.verbose, self.debug, self.header, self.split) |
|
|||
364 |
|
||||
365 | bq = getoutput # alias |
|
|||
366 |
|
||||
367 |
|
@@ -15,11 +15,14 b' Tests for platutils.py' | |||||
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 |
|
16 | |||
17 | import sys |
|
17 | import sys | |
|
18 | from unittest import TestCase | |||
18 |
|
19 | |||
19 | import nose.tools as nt |
|
20 | import nose.tools as nt | |
20 |
|
21 | |||
21 | from IPython.utils.process import find_cmd, FindCmdError, arg_split |
|
22 | from IPython.utils.process import (find_cmd, FindCmdError, arg_split, | |
|
23 | system, getoutput, getoutputerror) | |||
22 | from IPython.testing import decorators as dec |
|
24 | from IPython.testing import decorators as dec | |
|
25 | from IPython.testing import tools as tt | |||
23 |
|
26 | |||
24 | #----------------------------------------------------------------------------- |
|
27 | #----------------------------------------------------------------------------- | |
25 | # Tests |
|
28 | # Tests | |
@@ -66,3 +69,27 b' def test_arg_split():' | |||||
66 | ] |
|
69 | ] | |
67 | for argstr, argv in tests: |
|
70 | for argstr, argv in tests: | |
68 | nt.assert_equal(arg_split(argstr), argv) |
|
71 | nt.assert_equal(arg_split(argstr), argv) | |
|
72 | ||||
|
73 | ||||
|
74 | class SubProcessTestCase(TestCase, tt.TempFileMixin): | |||
|
75 | def setUp(self): | |||
|
76 | """Make a valid python temp file.""" | |||
|
77 | lines = ["from __future__ import print_function", | |||
|
78 | "import sys", | |||
|
79 | "print('on stdout', end='', file=sys.stdout)", | |||
|
80 | "print('on stderr', end='', file=sys.stderr)", | |||
|
81 | "sys.stdout.flush()", | |||
|
82 | "sys.stderr.flush()"] | |||
|
83 | self.mktmp('\n'.join(lines)) | |||
|
84 | ||||
|
85 | def test_system(self): | |||
|
86 | system('python "%s"' % self.fname) | |||
|
87 | ||||
|
88 | def test_getoutput(self): | |||
|
89 | out = getoutput('python "%s"' % self.fname) | |||
|
90 | self.assertEquals(out, 'on stdout') | |||
|
91 | ||||
|
92 | def test_getoutput(self): | |||
|
93 | out, err = getoutputerror('python "%s"' % self.fname) | |||
|
94 | self.assertEquals(out, 'on stdout') | |||
|
95 | self.assertEquals(err, 'on stderr') |
@@ -19,9 +19,6 b' from __future__ import print_function' | |||||
19 | import inspect |
|
19 | import inspect | |
20 | import os |
|
20 | import os | |
21 | import re |
|
21 | import re | |
22 | import sys |
|
|||
23 |
|
||||
24 | from subprocess import Popen, PIPE |
|
|||
25 |
|
22 | |||
26 | # Our own |
|
23 | # Our own | |
27 | from IPython.core.interactiveshell import ( |
|
24 | from IPython.core.interactiveshell import ( | |
@@ -83,26 +80,6 b' class ZMQInteractiveShell(InteractiveShell):' | |||||
83 |
|
80 | |||
84 | displayhook_class = Type(ZMQDisplayHook) |
|
81 | displayhook_class = Type(ZMQDisplayHook) | |
85 |
|
82 | |||
86 | def system(self, cmd): |
|
|||
87 | cmd = self.var_expand(cmd, depth=2).strip() |
|
|||
88 |
|
||||
89 | # Runnning a bacgkrounded process from within the gui isn't supported |
|
|||
90 | # because we do p.wait() at the end. So instead of silently blocking |
|
|||
91 | # we simply refuse to run in this mode, to avoid surprising the user. |
|
|||
92 | if cmd.endswith('&'): |
|
|||
93 | raise OSError("Background processes not supported.") |
|
|||
94 |
|
||||
95 | sys.stdout.flush() |
|
|||
96 | sys.stderr.flush() |
|
|||
97 | p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) |
|
|||
98 | for line in p.stdout.read().split('\n'): |
|
|||
99 | if len(line) > 0: |
|
|||
100 | print(line) |
|
|||
101 | for line in p.stderr.read().split('\n'): |
|
|||
102 | if len(line) > 0: |
|
|||
103 | print(line, file=sys.stderr) |
|
|||
104 | p.wait() |
|
|||
105 |
|
||||
106 | def init_io(self): |
|
83 | def init_io(self): | |
107 | # This will just use sys.stdout and sys.stderr. If you want to |
|
84 | # This will just use sys.stdout and sys.stderr. If you want to | |
108 | # override sys.stdout and sys.stderr themselves, you need to do that |
|
85 | # override sys.stdout and sys.stderr themselves, you need to do that |
General Comments 0
You need to be logged in to leave comments.
Login now