##// END OF EJS Templates
Moving posix version of arg_split to _process_common.
Jörgen Stenarson -
Show More
@@ -1,145 +1,170 b''
1 """Common utilities for the various process_* implementations.
1 """Common utilities for the various process_* implementations.
2
2
3 This file is only meant to be imported by the platform-specific implementations
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.
4 of subprocess utilities, and it contains tools that are common to all of them.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 import subprocess
17 import subprocess
18 import shlex
18 import sys
19 import sys
19
20
20 from IPython.utils import py3compat
21 from IPython.utils import py3compat
21
22
22 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
23 # Function definitions
24 # Function definitions
24 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
25
26
26 def read_no_interrupt(p):
27 def read_no_interrupt(p):
27 """Read from a pipe ignoring EINTR errors.
28 """Read from a pipe ignoring EINTR errors.
28
29
29 This is necessary because when reading from pipes with GUI event loops
30 This is necessary because when reading from pipes with GUI event loops
30 running in the background, often interrupts are raised that stop the
31 running in the background, often interrupts are raised that stop the
31 command from completing."""
32 command from completing."""
32 import errno
33 import errno
33
34
34 try:
35 try:
35 return p.read()
36 return p.read()
36 except IOError, err:
37 except IOError, err:
37 if err.errno != errno.EINTR:
38 if err.errno != errno.EINTR:
38 raise
39 raise
39
40
40
41
41 def process_handler(cmd, callback, stderr=subprocess.PIPE):
42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
42 """Open a command in a shell subprocess and execute a callback.
43 """Open a command in a shell subprocess and execute a callback.
43
44
44 This function provides common scaffolding for creating subprocess.Popen()
45 This function provides common scaffolding for creating subprocess.Popen()
45 calls. It creates a Popen object and then calls the callback with it.
46 calls. It creates a Popen object and then calls the callback with it.
46
47
47 Parameters
48 Parameters
48 ----------
49 ----------
49 cmd : str
50 cmd : str
50 A string to be executed with the underlying system shell (by calling
51 A string to be executed with the underlying system shell (by calling
51 :func:`Popen` with ``shell=True``.
52 :func:`Popen` with ``shell=True``.
52
53
53 callback : callable
54 callback : callable
54 A one-argument function that will be called with the Popen object.
55 A one-argument function that will be called with the Popen object.
55
56
56 stderr : file descriptor number, optional
57 stderr : file descriptor number, optional
57 By default this is set to ``subprocess.PIPE``, but you can also pass the
58 By default this is set to ``subprocess.PIPE``, but you can also pass the
58 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
59 the same file descriptor as its stdout. This is useful to read stdout
60 the same file descriptor as its stdout. This is useful to read stdout
60 and stderr combined in the order they are generated.
61 and stderr combined in the order they are generated.
61
62
62 Returns
63 Returns
63 -------
64 -------
64 The return value of the provided callback is returned.
65 The return value of the provided callback is returned.
65 """
66 """
66 sys.stdout.flush()
67 sys.stdout.flush()
67 sys.stderr.flush()
68 sys.stderr.flush()
68 # On win32, close_fds can't be true when using pipes for stdin/out/err
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
69 close_fds = sys.platform != 'win32'
70 close_fds = sys.platform != 'win32'
70 p = subprocess.Popen(cmd, shell=True,
71 p = subprocess.Popen(cmd, shell=True,
71 stdin=subprocess.PIPE,
72 stdin=subprocess.PIPE,
72 stdout=subprocess.PIPE,
73 stdout=subprocess.PIPE,
73 stderr=stderr,
74 stderr=stderr,
74 close_fds=close_fds)
75 close_fds=close_fds)
75
76
76 try:
77 try:
77 out = callback(p)
78 out = callback(p)
78 except KeyboardInterrupt:
79 except KeyboardInterrupt:
79 print('^C')
80 print('^C')
80 sys.stdout.flush()
81 sys.stdout.flush()
81 sys.stderr.flush()
82 sys.stderr.flush()
82 out = None
83 out = None
83 finally:
84 finally:
84 # Make really sure that we don't leave processes behind, in case the
85 # Make really sure that we don't leave processes behind, in case the
85 # call above raises an exception
86 # call above raises an exception
86 # We start by assuming the subprocess finished (to avoid NameErrors
87 # We start by assuming the subprocess finished (to avoid NameErrors
87 # later depending on the path taken)
88 # later depending on the path taken)
88 if p.returncode is None:
89 if p.returncode is None:
89 try:
90 try:
90 p.terminate()
91 p.terminate()
91 p.poll()
92 p.poll()
92 except OSError:
93 except OSError:
93 pass
94 pass
94 # One last try on our way out
95 # One last try on our way out
95 if p.returncode is None:
96 if p.returncode is None:
96 try:
97 try:
97 p.kill()
98 p.kill()
98 except OSError:
99 except OSError:
99 pass
100 pass
100
101
101 return out
102 return out
102
103
103
104
104 def getoutput(cmd):
105 def getoutput(cmd):
105 """Return standard output of executing cmd in a shell.
106 """Return standard output of executing cmd in a shell.
106
107
107 Accepts the same arguments as os.system().
108 Accepts the same arguments as os.system().
108
109
109 Parameters
110 Parameters
110 ----------
111 ----------
111 cmd : str
112 cmd : str
112 A command to be executed in the system shell.
113 A command to be executed in the system shell.
113
114
114 Returns
115 Returns
115 -------
116 -------
116 stdout : str
117 stdout : str
117 """
118 """
118
119
119 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
120 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
120 if out is None:
121 if out is None:
121 return ''
122 return ''
122 return py3compat.bytes_to_str(out)
123 return py3compat.bytes_to_str(out)
123
124
124
125
125 def getoutputerror(cmd):
126 def getoutputerror(cmd):
126 """Return (standard output, standard error) of executing cmd in a shell.
127 """Return (standard output, standard error) of executing cmd in a shell.
127
128
128 Accepts the same arguments as os.system().
129 Accepts the same arguments as os.system().
129
130
130 Parameters
131 Parameters
131 ----------
132 ----------
132 cmd : str
133 cmd : str
133 A command to be executed in the system shell.
134 A command to be executed in the system shell.
134
135
135 Returns
136 Returns
136 -------
137 -------
137 stdout : str
138 stdout : str
138 stderr : str
139 stderr : str
139 """
140 """
140
141
141 out_err = process_handler(cmd, lambda p: p.communicate())
142 out_err = process_handler(cmd, lambda p: p.communicate())
142 if out_err is None:
143 if out_err is None:
143 return '', ''
144 return '', ''
144 out, err = out_err
145 out, err = out_err
145 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
146 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
147
148
149 def arg_split(s, posix=False):
150 """Split a command line's arguments in a shell-like manner.
151
152 This is a modified version of the standard library's shlex.split()
153 function, but with a default of posix=False for splitting, so that quotes
154 in inputs are respected."""
155
156 # Unfortunately, python's shlex module is buggy with unicode input:
157 # http://bugs.python.org/issue1170
158 # At least encoding the input when it's unicode seems to help, but there
159 # may be more problems lurking. Apparently this is fixed in python3.
160 is_unicode = False
161 if (not py3compat.PY3) and isinstance(s, unicode):
162 is_unicode = True
163 s = s.encode('utf-8')
164 lex = shlex.shlex(s, posix=posix)
165 lex.whitespace_split = True
166 tokens = list(lex)
167 if is_unicode:
168 # Convert the tokens back to unicode.
169 tokens = [x.decode('utf-8') for x in tokens]
170 return tokens
@@ -1,221 +1,197 b''
1 """Posix-specific implementation of process utilities.
1 """Posix-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import subprocess as sp
19 import subprocess as sp
20 import sys
20 import sys
21 import shlex
22
21
23 from IPython.external import pexpect
22 from IPython.external import pexpect
24
23
25 # Our own
24 # Our own
26 from .autoattr import auto_attr
25 from .autoattr import auto_attr
27 from ._process_common import getoutput
26 from ._process_common import getoutput, arg_split
28 from IPython.utils import text
27 from IPython.utils import text
29 from IPython.utils import py3compat
28 from IPython.utils import py3compat
30
29
31 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
32 # Function definitions
31 # Function definitions
33 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
34
33
35 def _find_cmd(cmd):
34 def _find_cmd(cmd):
36 """Find the full path to a command using which."""
35 """Find the full path to a command using which."""
37
36
38 path = sp.Popen(['/usr/bin/env', 'which', cmd],
37 path = sp.Popen(['/usr/bin/env', 'which', cmd],
39 stdout=sp.PIPE).communicate()[0]
38 stdout=sp.PIPE).communicate()[0]
40 return py3compat.bytes_to_str(path)
39 return py3compat.bytes_to_str(path)
41
40
42
41
43 class ProcessHandler(object):
42 class ProcessHandler(object):
44 """Execute subprocesses under the control of pexpect.
43 """Execute subprocesses under the control of pexpect.
45 """
44 """
46 # Timeout in seconds to wait on each reading of the subprocess' output.
45 # Timeout in seconds to wait on each reading of the subprocess' output.
47 # This should not be set too low to avoid cpu overusage from our side,
46 # This should not be set too low to avoid cpu overusage from our side,
48 # since we read in a loop whose period is controlled by this timeout.
47 # since we read in a loop whose period is controlled by this timeout.
49 read_timeout = 0.05
48 read_timeout = 0.05
50
49
51 # Timeout to give a process if we receive SIGINT, between sending the
50 # Timeout to give a process if we receive SIGINT, between sending the
52 # SIGINT to the process and forcefully terminating it.
51 # SIGINT to the process and forcefully terminating it.
53 terminate_timeout = 0.2
52 terminate_timeout = 0.2
54
53
55 # File object where stdout and stderr of the subprocess will be written
54 # File object where stdout and stderr of the subprocess will be written
56 logfile = None
55 logfile = None
57
56
58 # Shell to call for subprocesses to execute
57 # Shell to call for subprocesses to execute
59 sh = None
58 sh = None
60
59
61 @auto_attr
60 @auto_attr
62 def sh(self):
61 def sh(self):
63 sh = pexpect.which('sh')
62 sh = pexpect.which('sh')
64 if sh is None:
63 if sh is None:
65 raise OSError('"sh" shell not found')
64 raise OSError('"sh" shell not found')
66 return sh
65 return sh
67
66
68 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
67 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
69 """Arguments are used for pexpect calls."""
68 """Arguments are used for pexpect calls."""
70 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
69 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
71 None else read_timeout)
70 None else read_timeout)
72 self.terminate_timeout = (ProcessHandler.terminate_timeout if
71 self.terminate_timeout = (ProcessHandler.terminate_timeout if
73 terminate_timeout is None else
72 terminate_timeout is None else
74 terminate_timeout)
73 terminate_timeout)
75 self.logfile = sys.stdout if logfile is None else logfile
74 self.logfile = sys.stdout if logfile is None else logfile
76
75
77 def getoutput(self, cmd):
76 def getoutput(self, cmd):
78 """Run a command and return its stdout/stderr as a string.
77 """Run a command and return its stdout/stderr as a string.
79
78
80 Parameters
79 Parameters
81 ----------
80 ----------
82 cmd : str
81 cmd : str
83 A command to be executed in the system shell.
82 A command to be executed in the system shell.
84
83
85 Returns
84 Returns
86 -------
85 -------
87 output : str
86 output : str
88 A string containing the combination of stdout and stderr from the
87 A string containing the combination of stdout and stderr from the
89 subprocess, in whatever order the subprocess originally wrote to its
88 subprocess, in whatever order the subprocess originally wrote to its
90 file descriptors (so the order of the information in this string is the
89 file descriptors (so the order of the information in this string is the
91 correct order as would be seen if running the command in a terminal).
90 correct order as would be seen if running the command in a terminal).
92 """
91 """
93 try:
92 try:
94 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
93 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
95 except KeyboardInterrupt:
94 except KeyboardInterrupt:
96 print('^C', file=sys.stderr, end='')
95 print('^C', file=sys.stderr, end='')
97
96
98 def getoutput_pexpect(self, cmd):
97 def getoutput_pexpect(self, cmd):
99 """Run a command and return its stdout/stderr as a string.
98 """Run a command and return its stdout/stderr as a string.
100
99
101 Parameters
100 Parameters
102 ----------
101 ----------
103 cmd : str
102 cmd : str
104 A command to be executed in the system shell.
103 A command to be executed in the system shell.
105
104
106 Returns
105 Returns
107 -------
106 -------
108 output : str
107 output : str
109 A string containing the combination of stdout and stderr from the
108 A string containing the combination of stdout and stderr from the
110 subprocess, in whatever order the subprocess originally wrote to its
109 subprocess, in whatever order the subprocess originally wrote to its
111 file descriptors (so the order of the information in this string is the
110 file descriptors (so the order of the information in this string is the
112 correct order as would be seen if running the command in a terminal).
111 correct order as would be seen if running the command in a terminal).
113 """
112 """
114 try:
113 try:
115 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
114 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
116 except KeyboardInterrupt:
115 except KeyboardInterrupt:
117 print('^C', file=sys.stderr, end='')
116 print('^C', file=sys.stderr, end='')
118
117
119 def system(self, cmd):
118 def system(self, cmd):
120 """Execute a command in a subshell.
119 """Execute a command in a subshell.
121
120
122 Parameters
121 Parameters
123 ----------
122 ----------
124 cmd : str
123 cmd : str
125 A command to be executed in the system shell.
124 A command to be executed in the system shell.
126
125
127 Returns
126 Returns
128 -------
127 -------
129 int : child's exitstatus
128 int : child's exitstatus
130 """
129 """
131 # Get likely encoding for the output.
130 # Get likely encoding for the output.
132 enc = text.getdefaultencoding()
131 enc = text.getdefaultencoding()
133
132
134 # Patterns to match on the output, for pexpect. We read input and
133 # Patterns to match on the output, for pexpect. We read input and
135 # allow either a short timeout or EOF
134 # allow either a short timeout or EOF
136 patterns = [pexpect.TIMEOUT, pexpect.EOF]
135 patterns = [pexpect.TIMEOUT, pexpect.EOF]
137 # the index of the EOF pattern in the list.
136 # the index of the EOF pattern in the list.
138 # even though we know it's 1, this call means we don't have to worry if
137 # even though we know it's 1, this call means we don't have to worry if
139 # we change the above list, and forget to change this value:
138 # we change the above list, and forget to change this value:
140 EOF_index = patterns.index(pexpect.EOF)
139 EOF_index = patterns.index(pexpect.EOF)
141 # The size of the output stored so far in the process output buffer.
140 # The size of the output stored so far in the process output buffer.
142 # Since pexpect only appends to this buffer, each time we print we
141 # Since pexpect only appends to this buffer, each time we print we
143 # record how far we've printed, so that next time we only print *new*
142 # record how far we've printed, so that next time we only print *new*
144 # content from the buffer.
143 # content from the buffer.
145 out_size = 0
144 out_size = 0
146 try:
145 try:
147 # Since we're not really searching the buffer for text patterns, we
146 # Since we're not really searching the buffer for text patterns, we
148 # can set pexpect's search window to be tiny and it won't matter.
147 # can set pexpect's search window to be tiny and it won't matter.
149 # We only search for the 'patterns' timeout or EOF, which aren't in
148 # We only search for the 'patterns' timeout or EOF, which aren't in
150 # the text itself.
149 # the text itself.
151 #child = pexpect.spawn(pcmd, searchwindowsize=1)
150 #child = pexpect.spawn(pcmd, searchwindowsize=1)
152 if hasattr(pexpect, 'spawnb'):
151 if hasattr(pexpect, 'spawnb'):
153 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
152 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
154 else:
153 else:
155 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
154 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
156 flush = sys.stdout.flush
155 flush = sys.stdout.flush
157 while True:
156 while True:
158 # res is the index of the pattern that caused the match, so we
157 # res is the index of the pattern that caused the match, so we
159 # know whether we've finished (if we matched EOF) or not
158 # know whether we've finished (if we matched EOF) or not
160 res_idx = child.expect_list(patterns, self.read_timeout)
159 res_idx = child.expect_list(patterns, self.read_timeout)
161 print(child.before[out_size:].decode(enc, 'replace'), end='')
160 print(child.before[out_size:].decode(enc, 'replace'), end='')
162 flush()
161 flush()
163 if res_idx==EOF_index:
162 if res_idx==EOF_index:
164 break
163 break
165 # Update the pointer to what we've already printed
164 # Update the pointer to what we've already printed
166 out_size = len(child.before)
165 out_size = len(child.before)
167 except KeyboardInterrupt:
166 except KeyboardInterrupt:
168 # We need to send ^C to the process. The ascii code for '^C' is 3
167 # We need to send ^C to the process. The ascii code for '^C' is 3
169 # (the character is known as ETX for 'End of Text', see
168 # (the character is known as ETX for 'End of Text', see
170 # curses.ascii.ETX).
169 # curses.ascii.ETX).
171 child.sendline(chr(3))
170 child.sendline(chr(3))
172 # Read and print any more output the program might produce on its
171 # Read and print any more output the program might produce on its
173 # way out.
172 # way out.
174 try:
173 try:
175 out_size = len(child.before)
174 out_size = len(child.before)
176 child.expect_list(patterns, self.terminate_timeout)
175 child.expect_list(patterns, self.terminate_timeout)
177 print(child.before[out_size:].decode(enc, 'replace'), end='')
176 print(child.before[out_size:].decode(enc, 'replace'), end='')
178 sys.stdout.flush()
177 sys.stdout.flush()
179 except KeyboardInterrupt:
178 except KeyboardInterrupt:
180 # Impatient users tend to type it multiple times
179 # Impatient users tend to type it multiple times
181 pass
180 pass
182 finally:
181 finally:
183 # Ensure the subprocess really is terminated
182 # Ensure the subprocess really is terminated
184 child.terminate(force=True)
183 child.terminate(force=True)
185 # add isalive check, to ensure exitstatus is set:
184 # add isalive check, to ensure exitstatus is set:
186 child.isalive()
185 child.isalive()
187 return child.exitstatus
186 return child.exitstatus
188
187
189
188
190 # Make system() with a functional interface for outside use. Note that we use
189 # Make system() with a functional interface for outside use. Note that we use
191 # getoutput() from the _common utils, which is built on top of popen(). Using
190 # getoutput() from the _common utils, which is built on top of popen(). Using
192 # pexpect to get subprocess output produces difficult to parse output, since
191 # pexpect to get subprocess output produces difficult to parse output, since
193 # programs think they are talking to a tty and produce highly formatted output
192 # programs think they are talking to a tty and produce highly formatted output
194 # (ls is a good example) that makes them hard.
193 # (ls is a good example) that makes them hard.
195 system = ProcessHandler().system
194 system = ProcessHandler().system
196
195
197 def arg_split(s, posix=False):
198 """Split a command line's arguments in a shell-like manner.
199
200 This is a modified version of the standard library's shlex.split()
201 function, but with a default of posix=False for splitting, so that quotes
202 in inputs are respected."""
203
204 # Unfortunately, python's shlex module is buggy with unicode input:
205 # http://bugs.python.org/issue1170
206 # At least encoding the input when it's unicode seems to help, but there
207 # may be more problems lurking. Apparently this is fixed in python3.
208 is_unicode = False
209 if (not py3compat.PY3) and isinstance(s, unicode):
210 is_unicode = True
211 s = s.encode('utf-8')
212 lex = shlex.shlex(s, posix=posix)
213 lex.whitespace_split = True
214 tokens = list(lex)
215 if is_unicode:
216 # Convert the tokens back to unicode.
217 tokens = [x.decode('utf-8') for x in tokens]
218 return tokens
219
220
196
221
197
@@ -1,201 +1,178 b''
1 """Windows-specific implementation of process utilities.
1 """Windows-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # stdlib
18 # stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import ctypes
21 import ctypes
22
22
23 from ctypes import c_int, POINTER
23 from ctypes import c_int, POINTER
24 from ctypes.wintypes import LPCWSTR, HLOCAL
24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 from subprocess import STDOUT
25 from subprocess import STDOUT
26
26
27 # our own imports
27 # our own imports
28 from ._process_common import read_no_interrupt, process_handler
28 from ._process_common import read_no_interrupt, process_handler
29 from . import py3compat
29 from . import py3compat
30 from . import text
30 from . import text
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Function definitions
33 # Function definitions
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class AvoidUNCPath(object):
36 class AvoidUNCPath(object):
37 """A context manager to protect command execution from UNC paths.
37 """A context manager to protect command execution from UNC paths.
38
38
39 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
39 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 This context manager temporarily changes directory to the 'C:' drive on
40 This context manager temporarily changes directory to the 'C:' drive on
41 entering, and restores the original working directory on exit.
41 entering, and restores the original working directory on exit.
42
42
43 The context manager returns the starting working directory *if* it made a
43 The context manager returns the starting working directory *if* it made a
44 change and None otherwise, so that users can apply the necessary adjustment
44 change and None otherwise, so that users can apply the necessary adjustment
45 to their system calls in the event of a change.
45 to their system calls in the event of a change.
46
46
47 Example
47 Example
48 -------
48 -------
49 ::
49 ::
50 cmd = 'dir'
50 cmd = 'dir'
51 with AvoidUNCPath() as path:
51 with AvoidUNCPath() as path:
52 if path is not None:
52 if path is not None:
53 cmd = '"pushd %s &&"%s' % (path, cmd)
53 cmd = '"pushd %s &&"%s' % (path, cmd)
54 os.system(cmd)
54 os.system(cmd)
55 """
55 """
56 def __enter__(self):
56 def __enter__(self):
57 self.path = os.getcwdu()
57 self.path = os.getcwdu()
58 self.is_unc_path = self.path.startswith(r"\\")
58 self.is_unc_path = self.path.startswith(r"\\")
59 if self.is_unc_path:
59 if self.is_unc_path:
60 # change to c drive (as cmd.exe cannot handle UNC addresses)
60 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 os.chdir("C:")
61 os.chdir("C:")
62 return self.path
62 return self.path
63 else:
63 else:
64 # We return None to signal that there was no change in the working
64 # We return None to signal that there was no change in the working
65 # directory
65 # directory
66 return None
66 return None
67
67
68 def __exit__(self, exc_type, exc_value, traceback):
68 def __exit__(self, exc_type, exc_value, traceback):
69 if self.is_unc_path:
69 if self.is_unc_path:
70 os.chdir(self.path)
70 os.chdir(self.path)
71
71
72
72
73 def _find_cmd(cmd):
73 def _find_cmd(cmd):
74 """Find the full path to a .bat or .exe using the win32api module."""
74 """Find the full path to a .bat or .exe using the win32api module."""
75 try:
75 try:
76 from win32api import SearchPath
76 from win32api import SearchPath
77 except ImportError:
77 except ImportError:
78 raise ImportError('you need to have pywin32 installed for this to work')
78 raise ImportError('you need to have pywin32 installed for this to work')
79 else:
79 else:
80 PATH = os.environ['PATH']
80 PATH = os.environ['PATH']
81 extensions = ['.exe', '.com', '.bat', '.py']
81 extensions = ['.exe', '.com', '.bat', '.py']
82 path = None
82 path = None
83 for ext in extensions:
83 for ext in extensions:
84 try:
84 try:
85 path = SearchPath(PATH, cmd + ext)[0]
85 path = SearchPath(PATH, cmd + ext)[0]
86 except:
86 except:
87 pass
87 pass
88 if path is None:
88 if path is None:
89 raise OSError("command %r not found" % cmd)
89 raise OSError("command %r not found" % cmd)
90 else:
90 else:
91 return path
91 return path
92
92
93
93
94 def _system_body(p):
94 def _system_body(p):
95 """Callback for _system."""
95 """Callback for _system."""
96 enc = text.getdefaultencoding()
96 enc = text.getdefaultencoding()
97 for line in read_no_interrupt(p.stdout).splitlines():
97 for line in read_no_interrupt(p.stdout).splitlines():
98 line = line.decode(enc, 'replace')
98 line = line.decode(enc, 'replace')
99 print(line, file=sys.stdout)
99 print(line, file=sys.stdout)
100 for line in read_no_interrupt(p.stderr).splitlines():
100 for line in read_no_interrupt(p.stderr).splitlines():
101 line = line.decode(enc, 'replace')
101 line = line.decode(enc, 'replace')
102 print(line, file=sys.stderr)
102 print(line, file=sys.stderr)
103
103
104 # Wait to finish for returncode
104 # Wait to finish for returncode
105 return p.wait()
105 return p.wait()
106
106
107
107
108 def system(cmd):
108 def system(cmd):
109 """Win32 version of os.system() that works with network shares.
109 """Win32 version of os.system() that works with network shares.
110
110
111 Note that this implementation returns None, as meant for use in IPython.
111 Note that this implementation returns None, as meant for use in IPython.
112
112
113 Parameters
113 Parameters
114 ----------
114 ----------
115 cmd : str
115 cmd : str
116 A command to be executed in the system shell.
116 A command to be executed in the system shell.
117
117
118 Returns
118 Returns
119 -------
119 -------
120 None : we explicitly do NOT return the subprocess status code, as this
120 None : we explicitly do NOT return the subprocess status code, as this
121 utility is meant to be used extensively in IPython, where any return value
121 utility is meant to be used extensively in IPython, where any return value
122 would trigger :func:`sys.displayhook` calls.
122 would trigger :func:`sys.displayhook` calls.
123 """
123 """
124 with AvoidUNCPath() as path:
124 with AvoidUNCPath() as path:
125 if path is not None:
125 if path is not None:
126 cmd = '"pushd %s &&"%s' % (path, cmd)
126 cmd = '"pushd %s &&"%s' % (path, cmd)
127 return process_handler(cmd, _system_body)
127 return process_handler(cmd, _system_body)
128
128
129
129
130 def getoutput(cmd):
130 def getoutput(cmd):
131 """Return standard output of executing cmd in a shell.
131 """Return standard output of executing cmd in a shell.
132
132
133 Accepts the same arguments as os.system().
133 Accepts the same arguments as os.system().
134
134
135 Parameters
135 Parameters
136 ----------
136 ----------
137 cmd : str
137 cmd : str
138 A command to be executed in the system shell.
138 A command to be executed in the system shell.
139
139
140 Returns
140 Returns
141 -------
141 -------
142 stdout : str
142 stdout : str
143 """
143 """
144
144
145 with AvoidUNCPath() as path:
145 with AvoidUNCPath() as path:
146 if path is not None:
146 if path is not None:
147 cmd = '"pushd %s &&"%s' % (path, cmd)
147 cmd = '"pushd %s &&"%s' % (path, cmd)
148 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
148 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
149
149
150 if out is None:
150 if out is None:
151 out = ''
151 out = ''
152 return out
152 return out
153
153
154 try:
154 try:
155 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
155 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
156 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
156 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
157 CommandLineToArgvW.res_types = [POINTER(LPCWSTR)]
157 CommandLineToArgvW.res_types = [POINTER(LPCWSTR)]
158 LocalFree = ctypes.windll.kernel32.LocalFree
158 LocalFree = ctypes.windll.kernel32.LocalFree
159 LocalFree.res_type = HLOCAL
159 LocalFree.res_type = HLOCAL
160 LocalFree.arg_types = [HLOCAL]
160 LocalFree.arg_types = [HLOCAL]
161
161
162 def arg_split(commandline, posix=False):
162 def arg_split(commandline, posix=False):
163 """Split a command line's arguments in a shell-like manner.
163 """Split a command line's arguments in a shell-like manner.
164
164
165 This is a special version for windows that use a ctypes call to CommandLineToArgvW
165 This is a special version for windows that use a ctypes call to CommandLineToArgvW
166 to do the argv splitting. The posix paramter is ignored.
166 to do the argv splitting. The posix paramter is ignored.
167 """
167 """
168 #CommandLineToArgvW returns path to executable if called with empty string.
168 #CommandLineToArgvW returns path to executable if called with empty string.
169 if commandline.strip() == "":
169 if commandline.strip() == "":
170 return []
170 return []
171 argvn = c_int()
171 argvn = c_int()
172 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
172 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
173 result_array_type = LPCWSTR * argvn.value
173 result_array_type = LPCWSTR * argvn.value
174 result = [arg for arg in result_array_type.from_address(result_pointer)]
174 result = [arg for arg in result_array_type.from_address(result_pointer)]
175 retval = LocalFree(result_pointer)
175 retval = LocalFree(result_pointer)
176 return result
176 return result
177 except AttributeError:
177 except AttributeError:
178 import shlex
178 from ._process_common import arg_split
179 #alternative if CommandLineToArgvW is not available
180 def arg_split(s, posix=False):
181 """Split a command line's arguments in a shell-like manner.
182
183 This is a modified version of the standard library's shlex.split()
184 function, but with a default of posix=False for splitting, so that quotes
185 in inputs are respected."""
186
187 # Unfortunately, python's shlex module is buggy with unicode input:
188 # http://bugs.python.org/issue1170
189 # At least encoding the input when it's unicode seems to help, but there
190 # may be more problems lurking. Apparently this is fixed in python3.
191 is_unicode = False
192 if (not py3compat.PY3) and isinstance(s, unicode):
193 is_unicode = True
194 s = s.encode('utf-8')
195 lex = shlex.shlex(s, posix=posix)
196 lex.whitespace_split = True
197 tokens = list(lex)
198 if is_unicode:
199 # Convert the tokens back to unicode.
200 tokens = [x.decode('utf-8') for x in tokens]
201 return tokens
General Comments 0
You need to be logged in to leave comments. Login now