##// END OF EJS Templates
Moved arg_split to _process_win32.py and _process_posix.py.
Jörgen Stenarson -
Show More
@@ -1,194 +1,221 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
21
22
22 from IPython.external import pexpect
23 from IPython.external import pexpect
23
24
24 # Our own
25 # Our own
25 from .autoattr import auto_attr
26 from .autoattr import auto_attr
26 from ._process_common import getoutput
27 from ._process_common import getoutput
27 from IPython.utils import text
28 from IPython.utils import text
28 from IPython.utils import py3compat
29 from IPython.utils import py3compat
29
30
30 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
31 # Function definitions
32 # Function definitions
32 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
33
34
34 def _find_cmd(cmd):
35 def _find_cmd(cmd):
35 """Find the full path to a command using which."""
36 """Find the full path to a command using which."""
36
37
37 path = sp.Popen(['/usr/bin/env', 'which', cmd],
38 path = sp.Popen(['/usr/bin/env', 'which', cmd],
38 stdout=sp.PIPE).communicate()[0]
39 stdout=sp.PIPE).communicate()[0]
39 return py3compat.bytes_to_str(path)
40 return py3compat.bytes_to_str(path)
40
41
41
42
42 class ProcessHandler(object):
43 class ProcessHandler(object):
43 """Execute subprocesses under the control of pexpect.
44 """Execute subprocesses under the control of pexpect.
44 """
45 """
45 # Timeout in seconds to wait on each reading of the subprocess' output.
46 # Timeout in seconds to wait on each reading of the subprocess' output.
46 # This should not be set too low to avoid cpu overusage from our side,
47 # This should not be set too low to avoid cpu overusage from our side,
47 # since we read in a loop whose period is controlled by this timeout.
48 # since we read in a loop whose period is controlled by this timeout.
48 read_timeout = 0.05
49 read_timeout = 0.05
49
50
50 # Timeout to give a process if we receive SIGINT, between sending the
51 # Timeout to give a process if we receive SIGINT, between sending the
51 # SIGINT to the process and forcefully terminating it.
52 # SIGINT to the process and forcefully terminating it.
52 terminate_timeout = 0.2
53 terminate_timeout = 0.2
53
54
54 # File object where stdout and stderr of the subprocess will be written
55 # File object where stdout and stderr of the subprocess will be written
55 logfile = None
56 logfile = None
56
57
57 # Shell to call for subprocesses to execute
58 # Shell to call for subprocesses to execute
58 sh = None
59 sh = None
59
60
60 @auto_attr
61 @auto_attr
61 def sh(self):
62 def sh(self):
62 sh = pexpect.which('sh')
63 sh = pexpect.which('sh')
63 if sh is None:
64 if sh is None:
64 raise OSError('"sh" shell not found')
65 raise OSError('"sh" shell not found')
65 return sh
66 return sh
66
67
67 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
68 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
68 """Arguments are used for pexpect calls."""
69 """Arguments are used for pexpect calls."""
69 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
70 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
70 None else read_timeout)
71 None else read_timeout)
71 self.terminate_timeout = (ProcessHandler.terminate_timeout if
72 self.terminate_timeout = (ProcessHandler.terminate_timeout if
72 terminate_timeout is None else
73 terminate_timeout is None else
73 terminate_timeout)
74 terminate_timeout)
74 self.logfile = sys.stdout if logfile is None else logfile
75 self.logfile = sys.stdout if logfile is None else logfile
75
76
76 def getoutput(self, cmd):
77 def getoutput(self, cmd):
77 """Run a command and return its stdout/stderr as a string.
78 """Run a command and return its stdout/stderr as a string.
78
79
79 Parameters
80 Parameters
80 ----------
81 ----------
81 cmd : str
82 cmd : str
82 A command to be executed in the system shell.
83 A command to be executed in the system shell.
83
84
84 Returns
85 Returns
85 -------
86 -------
86 output : str
87 output : str
87 A string containing the combination of stdout and stderr from the
88 A string containing the combination of stdout and stderr from the
88 subprocess, in whatever order the subprocess originally wrote to its
89 subprocess, in whatever order the subprocess originally wrote to its
89 file descriptors (so the order of the information in this string is the
90 file descriptors (so the order of the information in this string is the
90 correct order as would be seen if running the command in a terminal).
91 correct order as would be seen if running the command in a terminal).
91 """
92 """
92 try:
93 try:
93 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
94 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
94 except KeyboardInterrupt:
95 except KeyboardInterrupt:
95 print('^C', file=sys.stderr, end='')
96 print('^C', file=sys.stderr, end='')
96
97
97 def getoutput_pexpect(self, cmd):
98 def getoutput_pexpect(self, cmd):
98 """Run a command and return its stdout/stderr as a string.
99 """Run a command and return its stdout/stderr as a string.
99
100
100 Parameters
101 Parameters
101 ----------
102 ----------
102 cmd : str
103 cmd : str
103 A command to be executed in the system shell.
104 A command to be executed in the system shell.
104
105
105 Returns
106 Returns
106 -------
107 -------
107 output : str
108 output : str
108 A string containing the combination of stdout and stderr from the
109 A string containing the combination of stdout and stderr from the
109 subprocess, in whatever order the subprocess originally wrote to its
110 subprocess, in whatever order the subprocess originally wrote to its
110 file descriptors (so the order of the information in this string is the
111 file descriptors (so the order of the information in this string is the
111 correct order as would be seen if running the command in a terminal).
112 correct order as would be seen if running the command in a terminal).
112 """
113 """
113 try:
114 try:
114 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
115 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
115 except KeyboardInterrupt:
116 except KeyboardInterrupt:
116 print('^C', file=sys.stderr, end='')
117 print('^C', file=sys.stderr, end='')
117
118
118 def system(self, cmd):
119 def system(self, cmd):
119 """Execute a command in a subshell.
120 """Execute a command in a subshell.
120
121
121 Parameters
122 Parameters
122 ----------
123 ----------
123 cmd : str
124 cmd : str
124 A command to be executed in the system shell.
125 A command to be executed in the system shell.
125
126
126 Returns
127 Returns
127 -------
128 -------
128 int : child's exitstatus
129 int : child's exitstatus
129 """
130 """
130 # Get likely encoding for the output.
131 # Get likely encoding for the output.
131 enc = text.getdefaultencoding()
132 enc = text.getdefaultencoding()
132
133
133 # Patterns to match on the output, for pexpect. We read input and
134 # Patterns to match on the output, for pexpect. We read input and
134 # allow either a short timeout or EOF
135 # allow either a short timeout or EOF
135 patterns = [pexpect.TIMEOUT, pexpect.EOF]
136 patterns = [pexpect.TIMEOUT, pexpect.EOF]
136 # the index of the EOF pattern in the list.
137 # the index of the EOF pattern in the list.
137 # even though we know it's 1, this call means we don't have to worry if
138 # even though we know it's 1, this call means we don't have to worry if
138 # we change the above list, and forget to change this value:
139 # we change the above list, and forget to change this value:
139 EOF_index = patterns.index(pexpect.EOF)
140 EOF_index = patterns.index(pexpect.EOF)
140 # The size of the output stored so far in the process output buffer.
141 # The size of the output stored so far in the process output buffer.
141 # Since pexpect only appends to this buffer, each time we print we
142 # Since pexpect only appends to this buffer, each time we print we
142 # record how far we've printed, so that next time we only print *new*
143 # record how far we've printed, so that next time we only print *new*
143 # content from the buffer.
144 # content from the buffer.
144 out_size = 0
145 out_size = 0
145 try:
146 try:
146 # Since we're not really searching the buffer for text patterns, we
147 # Since we're not really searching the buffer for text patterns, we
147 # can set pexpect's search window to be tiny and it won't matter.
148 # can set pexpect's search window to be tiny and it won't matter.
148 # We only search for the 'patterns' timeout or EOF, which aren't in
149 # We only search for the 'patterns' timeout or EOF, which aren't in
149 # the text itself.
150 # the text itself.
150 #child = pexpect.spawn(pcmd, searchwindowsize=1)
151 #child = pexpect.spawn(pcmd, searchwindowsize=1)
151 if hasattr(pexpect, 'spawnb'):
152 if hasattr(pexpect, 'spawnb'):
152 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
153 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
153 else:
154 else:
154 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
155 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
155 flush = sys.stdout.flush
156 flush = sys.stdout.flush
156 while True:
157 while True:
157 # res is the index of the pattern that caused the match, so we
158 # res is the index of the pattern that caused the match, so we
158 # know whether we've finished (if we matched EOF) or not
159 # know whether we've finished (if we matched EOF) or not
159 res_idx = child.expect_list(patterns, self.read_timeout)
160 res_idx = child.expect_list(patterns, self.read_timeout)
160 print(child.before[out_size:].decode(enc, 'replace'), end='')
161 print(child.before[out_size:].decode(enc, 'replace'), end='')
161 flush()
162 flush()
162 if res_idx==EOF_index:
163 if res_idx==EOF_index:
163 break
164 break
164 # Update the pointer to what we've already printed
165 # Update the pointer to what we've already printed
165 out_size = len(child.before)
166 out_size = len(child.before)
166 except KeyboardInterrupt:
167 except KeyboardInterrupt:
167 # We need to send ^C to the process. The ascii code for '^C' is 3
168 # We need to send ^C to the process. The ascii code for '^C' is 3
168 # (the character is known as ETX for 'End of Text', see
169 # (the character is known as ETX for 'End of Text', see
169 # curses.ascii.ETX).
170 # curses.ascii.ETX).
170 child.sendline(chr(3))
171 child.sendline(chr(3))
171 # Read and print any more output the program might produce on its
172 # Read and print any more output the program might produce on its
172 # way out.
173 # way out.
173 try:
174 try:
174 out_size = len(child.before)
175 out_size = len(child.before)
175 child.expect_list(patterns, self.terminate_timeout)
176 child.expect_list(patterns, self.terminate_timeout)
176 print(child.before[out_size:].decode(enc, 'replace'), end='')
177 print(child.before[out_size:].decode(enc, 'replace'), end='')
177 sys.stdout.flush()
178 sys.stdout.flush()
178 except KeyboardInterrupt:
179 except KeyboardInterrupt:
179 # Impatient users tend to type it multiple times
180 # Impatient users tend to type it multiple times
180 pass
181 pass
181 finally:
182 finally:
182 # Ensure the subprocess really is terminated
183 # Ensure the subprocess really is terminated
183 child.terminate(force=True)
184 child.terminate(force=True)
184 # add isalive check, to ensure exitstatus is set:
185 # add isalive check, to ensure exitstatus is set:
185 child.isalive()
186 child.isalive()
186 return child.exitstatus
187 return child.exitstatus
187
188
188
189
189 # Make system() with a functional interface for outside use. Note that we use
190 # Make system() with a functional interface for outside use. Note that we use
190 # getoutput() from the _common utils, which is built on top of popen(). Using
191 # getoutput() from the _common utils, which is built on top of popen(). Using
191 # pexpect to get subprocess output produces difficult to parse output, since
192 # pexpect to get subprocess output produces difficult to parse output, since
192 # programs think they are talking to a tty and produce highly formatted output
193 # programs think they are talking to a tty and produce highly formatted output
193 # (ls is a good example) that makes them hard.
194 # (ls is a good example) that makes them hard.
194 system = ProcessHandler().system
195 system = ProcessHandler().system
196
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
221
@@ -1,148 +1,176 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
22
23 from ctypes import c_int, POINTER
24 from ctypes.wintypes import LPCWSTR, HLOCAL
22 from subprocess import STDOUT
25 from subprocess import STDOUT
23
26
24 # our own imports
27 # our own imports
25 from ._process_common import read_no_interrupt, process_handler
28 from ._process_common import read_no_interrupt, process_handler
29 from . import py3compat
26 from . import text
30 from . import text
27
31
28 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
29 # Function definitions
33 # Function definitions
30 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
31
35
32 class AvoidUNCPath(object):
36 class AvoidUNCPath(object):
33 """A context manager to protect command execution from UNC paths.
37 """A context manager to protect command execution from UNC paths.
34
38
35 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.
36 This context manager temporarily changes directory to the 'C:' drive on
40 This context manager temporarily changes directory to the 'C:' drive on
37 entering, and restores the original working directory on exit.
41 entering, and restores the original working directory on exit.
38
42
39 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
40 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
41 to their system calls in the event of a change.
45 to their system calls in the event of a change.
42
46
43 Example
47 Example
44 -------
48 -------
45 ::
49 ::
46 cmd = 'dir'
50 cmd = 'dir'
47 with AvoidUNCPath() as path:
51 with AvoidUNCPath() as path:
48 if path is not None:
52 if path is not None:
49 cmd = '"pushd %s &&"%s' % (path, cmd)
53 cmd = '"pushd %s &&"%s' % (path, cmd)
50 os.system(cmd)
54 os.system(cmd)
51 """
55 """
52 def __enter__(self):
56 def __enter__(self):
53 self.path = os.getcwdu()
57 self.path = os.getcwdu()
54 self.is_unc_path = self.path.startswith(r"\\")
58 self.is_unc_path = self.path.startswith(r"\\")
55 if self.is_unc_path:
59 if self.is_unc_path:
56 # change to c drive (as cmd.exe cannot handle UNC addresses)
60 # change to c drive (as cmd.exe cannot handle UNC addresses)
57 os.chdir("C:")
61 os.chdir("C:")
58 return self.path
62 return self.path
59 else:
63 else:
60 # 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
61 # directory
65 # directory
62 return None
66 return None
63
67
64 def __exit__(self, exc_type, exc_value, traceback):
68 def __exit__(self, exc_type, exc_value, traceback):
65 if self.is_unc_path:
69 if self.is_unc_path:
66 os.chdir(self.path)
70 os.chdir(self.path)
67
71
68
72
69 def _find_cmd(cmd):
73 def _find_cmd(cmd):
70 """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."""
71 try:
75 try:
72 from win32api import SearchPath
76 from win32api import SearchPath
73 except ImportError:
77 except ImportError:
74 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')
75 else:
79 else:
76 PATH = os.environ['PATH']
80 PATH = os.environ['PATH']
77 extensions = ['.exe', '.com', '.bat', '.py']
81 extensions = ['.exe', '.com', '.bat', '.py']
78 path = None
82 path = None
79 for ext in extensions:
83 for ext in extensions:
80 try:
84 try:
81 path = SearchPath(PATH, cmd + ext)[0]
85 path = SearchPath(PATH, cmd + ext)[0]
82 except:
86 except:
83 pass
87 pass
84 if path is None:
88 if path is None:
85 raise OSError("command %r not found" % cmd)
89 raise OSError("command %r not found" % cmd)
86 else:
90 else:
87 return path
91 return path
88
92
89
93
90 def _system_body(p):
94 def _system_body(p):
91 """Callback for _system."""
95 """Callback for _system."""
92 enc = text.getdefaultencoding()
96 enc = text.getdefaultencoding()
93 for line in read_no_interrupt(p.stdout).splitlines():
97 for line in read_no_interrupt(p.stdout).splitlines():
94 line = line.decode(enc, 'replace')
98 line = line.decode(enc, 'replace')
95 print(line, file=sys.stdout)
99 print(line, file=sys.stdout)
96 for line in read_no_interrupt(p.stderr).splitlines():
100 for line in read_no_interrupt(p.stderr).splitlines():
97 line = line.decode(enc, 'replace')
101 line = line.decode(enc, 'replace')
98 print(line, file=sys.stderr)
102 print(line, file=sys.stderr)
99
103
100 # Wait to finish for returncode
104 # Wait to finish for returncode
101 return p.wait()
105 return p.wait()
102
106
103
107
104 def system(cmd):
108 def system(cmd):
105 """Win32 version of os.system() that works with network shares.
109 """Win32 version of os.system() that works with network shares.
106
110
107 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.
108
112
109 Parameters
113 Parameters
110 ----------
114 ----------
111 cmd : str
115 cmd : str
112 A command to be executed in the system shell.
116 A command to be executed in the system shell.
113
117
114 Returns
118 Returns
115 -------
119 -------
116 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
117 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
118 would trigger :func:`sys.displayhook` calls.
122 would trigger :func:`sys.displayhook` calls.
119 """
123 """
120 with AvoidUNCPath() as path:
124 with AvoidUNCPath() as path:
121 if path is not None:
125 if path is not None:
122 cmd = '"pushd %s &&"%s' % (path, cmd)
126 cmd = '"pushd %s &&"%s' % (path, cmd)
123 return process_handler(cmd, _system_body)
127 return process_handler(cmd, _system_body)
124
128
125
129
126 def getoutput(cmd):
130 def getoutput(cmd):
127 """Return standard output of executing cmd in a shell.
131 """Return standard output of executing cmd in a shell.
128
132
129 Accepts the same arguments as os.system().
133 Accepts the same arguments as os.system().
130
134
131 Parameters
135 Parameters
132 ----------
136 ----------
133 cmd : str
137 cmd : str
134 A command to be executed in the system shell.
138 A command to be executed in the system shell.
135
139
136 Returns
140 Returns
137 -------
141 -------
138 stdout : str
142 stdout : str
139 """
143 """
140
144
141 with AvoidUNCPath() as path:
145 with AvoidUNCPath() as path:
142 if path is not None:
146 if path is not None:
143 cmd = '"pushd %s &&"%s' % (path, cmd)
147 cmd = '"pushd %s &&"%s' % (path, cmd)
144 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
148 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
145
149
146 if out is None:
150 if out is None:
147 out = ''
151 out = ''
148 return out
152 return out
153
154
155 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
156 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
157 CommandLineToArgvW.res_types = [POINTER(LPCWSTR)]
158 LocalFree = ctypes.windll.kernel32.LocalFree
159 LocalFree.res_type = HLOCAL
160 LocalFree.arg_types = [HLOCAL]
161
162 def arg_split(commandline, posix=False):
163 """Split a command line's arguments in a shell-like manner.
164
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.
167 """
168 #CommandLineToArgvW returns path to executable if called with empty string.
169 if commandline.strip() == "":
170 return []
171 argvn = c_int()
172 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
173 result_array_type = LPCWSTR * argvn.value
174 result = [arg for arg in result_array_type.from_address(result_pointer)]
175 retval = LocalFree(result_pointer)
176 return result
@@ -1,169 +1,123 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with external processes.
3 Utilities for working with external processes.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-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 shlex
21 import shlex
22
22
23 # Our own
23 # Our own
24 if sys.platform == 'win32':
24 if sys.platform == 'win32':
25 import ctypes
25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split
26 from ctypes.wintypes import LPCWSTR, HLOCAL
27 from ctypes import c_int, POINTER
28 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath
29 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
30 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
31 CommandLineToArgvW.res_types = [POINTER(LPCWSTR)]
32 LocalFree = ctypes.windll.kernel32.LocalFree
33 LocalFree.res_type = HLOCAL
34 LocalFree.arg_types = [HLOCAL]
35 else:
26 else:
36 from ._process_posix import _find_cmd, system, getoutput
27 from ._process_posix import _find_cmd, system, getoutput, arg_split
28
37
29
38 from ._process_common import getoutputerror
30 from ._process_common import getoutputerror
39 from IPython.utils import py3compat
31 from IPython.utils import py3compat
40
32
41 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
42 # Code
34 # Code
43 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
44
36
45
37
46 class FindCmdError(Exception):
38 class FindCmdError(Exception):
47 pass
39 pass
48
40
49
41
50 def find_cmd(cmd):
42 def find_cmd(cmd):
51 """Find absolute path to executable cmd in a cross platform manner.
43 """Find absolute path to executable cmd in a cross platform manner.
52
44
53 This function tries to determine the full path to a command line program
45 This function tries to determine the full path to a command line program
54 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
55 time it will use the version that is first on the users `PATH`. If
47 time it will use the version that is first on the users `PATH`. If
56 cmd is `python` return `sys.executable`.
48 cmd is `python` return `sys.executable`.
57
49
58 Warning, don't use this to find IPython command line programs as there
50 Warning, don't use this to find IPython command line programs as there
59 is a risk you will find the wrong one. Instead find those using the
51 is a risk you will find the wrong one. Instead find those using the
60 following code and looking for the application itself::
52 following code and looking for the application itself::
61
53
62 from IPython.utils.path import get_ipython_module_path
54 from IPython.utils.path import get_ipython_module_path
63 from IPython.utils.process import pycmd2argv
55 from IPython.utils.process import pycmd2argv
64 argv = pycmd2argv(get_ipython_module_path('IPython.frontend.terminal.ipapp'))
56 argv = pycmd2argv(get_ipython_module_path('IPython.frontend.terminal.ipapp'))
65
57
66 Parameters
58 Parameters
67 ----------
59 ----------
68 cmd : str
60 cmd : str
69 The command line program to look for.
61 The command line program to look for.
70 """
62 """
71 if cmd == 'python':
63 if cmd == 'python':
72 return os.path.abspath(sys.executable)
64 return os.path.abspath(sys.executable)
73 try:
65 try:
74 path = _find_cmd(cmd).rstrip()
66 path = _find_cmd(cmd).rstrip()
75 except OSError:
67 except OSError:
76 raise FindCmdError('command could not be found: %s' % cmd)
68 raise FindCmdError('command could not be found: %s' % cmd)
77 # which returns empty if not found
69 # which returns empty if not found
78 if path == '':
70 if path == '':
79 raise FindCmdError('command could not be found: %s' % cmd)
71 raise FindCmdError('command could not be found: %s' % cmd)
80 return os.path.abspath(path)
72 return os.path.abspath(path)
81
73
82
74
83 def pycmd2argv(cmd):
75 def pycmd2argv(cmd):
84 r"""Take the path of a python command and return a list (argv-style).
76 r"""Take the path of a python command and return a list (argv-style).
85
77
86 This only works on Python based command line programs and will find the
78 This only works on Python based command line programs and will find the
87 location of the ``python`` executable using ``sys.executable`` to make
79 location of the ``python`` executable using ``sys.executable`` to make
88 sure the right version is used.
80 sure the right version is used.
89
81
90 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
82 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
91 .com or .bat, and [, cmd] otherwise.
83 .com or .bat, and [, cmd] otherwise.
92
84
93 Parameters
85 Parameters
94 ----------
86 ----------
95 cmd : string
87 cmd : string
96 The path of the command.
88 The path of the command.
97
89
98 Returns
90 Returns
99 -------
91 -------
100 argv-style list.
92 argv-style list.
101 """
93 """
102 ext = os.path.splitext(cmd)[1]
94 ext = os.path.splitext(cmd)[1]
103 if ext in ['.exe', '.com', '.bat']:
95 if ext in ['.exe', '.com', '.bat']:
104 return [cmd]
96 return [cmd]
105 else:
97 else:
106 if sys.platform == 'win32':
98 if sys.platform == 'win32':
107 # The -u option here turns on unbuffered output, which is required
99 # The -u option here turns on unbuffered output, which is required
108 # on Win32 to prevent wierd conflict and problems with Twisted.
100 # on Win32 to prevent wierd conflict and problems with Twisted.
109 # Also, use sys.executable to make sure we are picking up the
101 # Also, use sys.executable to make sure we are picking up the
110 # right python exe.
102 # right python exe.
111 return [sys.executable, '-u', cmd]
103 return [sys.executable, '-u', cmd]
112 else:
104 else:
113 return [sys.executable, cmd]
105 return [sys.executable, cmd]
114
106
115 if sys.platform == 'win32':
116 def arg_split(commandline, posix=False):
117 """Split a command line's arguments in a shell-like manner.
118
119 This is a special version for windows that use a ctypes call to CommandLineToArgvW
120 to do the argv splitting. The posix paramter is ignored.
121 """
122 argvn = c_int()
123 result_pointer = CommandLineToArgvW(py3compat.str_to_unicode(commandline.lstrip()), ctypes.byref(argvn))
124 result_array_type = LPCWSTR * argvn.value
125 result = [arg for arg in result_array_type.from_address(result_pointer)]
126 retval = LocalFree(result_pointer)
127 return result
128 else:
129 def arg_split(s, posix=False):
130 """Split a command line's arguments in a shell-like manner.
131
132 This is a modified version of the standard library's shlex.split()
133 function, but with a default of posix=False for splitting, so that quotes
134 in inputs are respected."""
135
136 # Unfortunately, python's shlex module is buggy with unicode input:
137 # http://bugs.python.org/issue1170
138 # At least encoding the input when it's unicode seems to help, but there
139 # may be more problems lurking. Apparently this is fixed in python3.
140 is_unicode = False
141 if (not py3compat.PY3) and isinstance(s, unicode):
142 is_unicode = True
143 s = s.encode('utf-8')
144 lex = shlex.shlex(s, posix=posix)
145 lex.whitespace_split = True
146 tokens = list(lex)
147 if is_unicode:
148 # Convert the tokens back to unicode.
149 tokens = [x.decode('utf-8') for x in tokens]
150 return tokens
151
152
153 def abbrev_cwd():
107 def abbrev_cwd():
154 """ Return abbreviated version of cwd, e.g. d:mydir """
108 """ Return abbreviated version of cwd, e.g. d:mydir """
155 cwd = os.getcwdu().replace('\\','/')
109 cwd = os.getcwdu().replace('\\','/')
156 drivepart = ''
110 drivepart = ''
157 tail = cwd
111 tail = cwd
158 if sys.platform == 'win32':
112 if sys.platform == 'win32':
159 if len(cwd) < 4:
113 if len(cwd) < 4:
160 return cwd
114 return cwd
161 drivepart,tail = os.path.splitdrive(cwd)
115 drivepart,tail = os.path.splitdrive(cwd)
162
116
163
117
164 parts = tail.split('/')
118 parts = tail.split('/')
165 if len(parts) > 2:
119 if len(parts) > 2:
166 tail = '/'.join(parts[-2:])
120 tail = '/'.join(parts[-2:])
167
121
168 return (drivepart + (
122 return (drivepart + (
169 cwd == '/' and '/' or tail))
123 cwd == '/' and '/' or tail))
@@ -1,128 +1,131 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Tests for platutils.py
3 Tests for platutils.py
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-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
16
17 import sys
17 import sys
18 from unittest import TestCase
18 from unittest import TestCase
19
19
20 import nose.tools as nt
20 import nose.tools as nt
21
21
22 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)
23 system, getoutput, getoutputerror)
24 from IPython.testing import decorators as dec
24 from IPython.testing import decorators as dec
25 from IPython.testing import tools as tt
25 from IPython.testing import tools as tt
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Tests
28 # Tests
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 def test_find_cmd_python():
31 def test_find_cmd_python():
32 """Make sure we find sys.exectable for python."""
32 """Make sure we find sys.exectable for python."""
33 nt.assert_equals(find_cmd('python'), sys.executable)
33 nt.assert_equals(find_cmd('python'), sys.executable)
34
34
35
35
36 @dec.skip_win32
36 @dec.skip_win32
37 def test_find_cmd_ls():
37 def test_find_cmd_ls():
38 """Make sure we can find the full path to ls."""
38 """Make sure we can find the full path to ls."""
39 path = find_cmd('ls')
39 path = find_cmd('ls')
40 nt.assert_true(path.endswith('ls'))
40 nt.assert_true(path.endswith('ls'))
41
41
42
42
43 def has_pywin32():
43 def has_pywin32():
44 try:
44 try:
45 import win32api
45 import win32api
46 except ImportError:
46 except ImportError:
47 return False
47 return False
48 return True
48 return True
49
49
50
50
51 @dec.onlyif(has_pywin32, "This test requires win32api to run")
51 @dec.onlyif(has_pywin32, "This test requires win32api to run")
52 def test_find_cmd_pythonw():
52 def test_find_cmd_pythonw():
53 """Try to find pythonw on Windows."""
53 """Try to find pythonw on Windows."""
54 path = find_cmd('pythonw')
54 path = find_cmd('pythonw')
55 nt.assert_true(path.endswith('pythonw.exe'))
55 nt.assert_true(path.endswith('pythonw.exe'))
56
56
57
57
58 @dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(),
58 @dec.onlyif(lambda : sys.platform != 'win32' or has_pywin32(),
59 "This test runs on posix or in win32 with win32api installed")
59 "This test runs on posix or in win32 with win32api installed")
60 def test_find_cmd_fail():
60 def test_find_cmd_fail():
61 """Make sure that FindCmdError is raised if we can't find the cmd."""
61 """Make sure that FindCmdError is raised if we can't find the cmd."""
62 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
62 nt.assert_raises(FindCmdError,find_cmd,'asdfasdf')
63
63
64
64
65 @dec.skip_win32
65 @dec.skip_win32
66 def test_arg_split():
66 def test_arg_split():
67 """Ensure that argument lines are correctly split like in a shell."""
67 """Ensure that argument lines are correctly split like in a shell."""
68 tests = [['hi', ['hi']],
68 tests = [['hi', ['hi']],
69 [u'hi', [u'hi']],
69 [u'hi', [u'hi']],
70 ['hello there', ['hello', 'there']],
70 ['hello there', ['hello', 'there']],
71 # [u'h\N{LATIN SMALL LETTER A WITH CARON}llo', [u'h\N{LATIN SMALL LETTER A WITH CARON}llo']],
71 # \u01ce == \N{LATIN SMALL LETTER A WITH CARON}
72 # Do not use \N because the tests crash with syntax error in
73 # some cases, for example windows python2.6.
74 [u'h\u01cello', [u'h\u01cello']],
72 ['something "with quotes"', ['something', '"with quotes"']],
75 ['something "with quotes"', ['something', '"with quotes"']],
73 ]
76 ]
74 for argstr, argv in tests:
77 for argstr, argv in tests:
75 nt.assert_equal(arg_split(argstr), argv)
78 nt.assert_equal(arg_split(argstr), argv)
76
79
77 @dec.skip_if_not_win32
80 @dec.skip_if_not_win32
78 def test_arg_split_win32():
81 def test_arg_split_win32():
79 """Ensure that argument lines are correctly split like in a shell."""
82 """Ensure that argument lines are correctly split like in a shell."""
80 tests = [['hi', ['hi']],
83 tests = [['hi', ['hi']],
81 [u'hi', [u'hi']],
84 [u'hi', [u'hi']],
82 ['hello there', ['hello', 'there']],
85 ['hello there', ['hello', 'there']],
83 # [u'h\N{LATIN SMALL LETTER A WITH CARON}llo', [u'h\N{LATIN SMALL LETTER A WITH CARON}llo']],
86 [u'h\u01cello', [u'h\u01cello']],
84 ['something "with quotes"', ['something', 'with quotes']],
87 ['something "with quotes"', ['something', 'with quotes']],
85 ]
88 ]
86 for argstr, argv in tests:
89 for argstr, argv in tests:
87 nt.assert_equal(arg_split(argstr), argv)
90 nt.assert_equal(arg_split(argstr), argv)
88
91
89
92
90 class SubProcessTestCase(TestCase, tt.TempFileMixin):
93 class SubProcessTestCase(TestCase, tt.TempFileMixin):
91 def setUp(self):
94 def setUp(self):
92 """Make a valid python temp file."""
95 """Make a valid python temp file."""
93 lines = ["from __future__ import print_function",
96 lines = ["from __future__ import print_function",
94 "import sys",
97 "import sys",
95 "print('on stdout', end='', file=sys.stdout)",
98 "print('on stdout', end='', file=sys.stdout)",
96 "print('on stderr', end='', file=sys.stderr)",
99 "print('on stderr', end='', file=sys.stderr)",
97 "sys.stdout.flush()",
100 "sys.stdout.flush()",
98 "sys.stderr.flush()"]
101 "sys.stderr.flush()"]
99 self.mktmp('\n'.join(lines))
102 self.mktmp('\n'.join(lines))
100
103
101 def test_system(self):
104 def test_system(self):
102 status = system('python "%s"' % self.fname)
105 status = system('python "%s"' % self.fname)
103 self.assertEquals(status, 0)
106 self.assertEquals(status, 0)
104
107
105 def test_system_quotes(self):
108 def test_system_quotes(self):
106 status = system('python -c "import sys"')
109 status = system('python -c "import sys"')
107 self.assertEquals(status, 0)
110 self.assertEquals(status, 0)
108
111
109 def test_getoutput(self):
112 def test_getoutput(self):
110 out = getoutput('python "%s"' % self.fname)
113 out = getoutput('python "%s"' % self.fname)
111 self.assertEquals(out, 'on stdout')
114 self.assertEquals(out, 'on stdout')
112
115
113 def test_getoutput_quoted(self):
116 def test_getoutput_quoted(self):
114 out = getoutput('python -c "print (1)"')
117 out = getoutput('python -c "print (1)"')
115 self.assertEquals(out.strip(), '1')
118 self.assertEquals(out.strip(), '1')
116
119
117 #Invalid quoting on windows
120 #Invalid quoting on windows
118 @dec.skip_win32
121 @dec.skip_win32
119 def test_getoutput_quoted2(self):
122 def test_getoutput_quoted2(self):
120 out = getoutput("python -c 'print (1)'")
123 out = getoutput("python -c 'print (1)'")
121 self.assertEquals(out.strip(), '1')
124 self.assertEquals(out.strip(), '1')
122 out = getoutput("python -c 'print (\"1\")'")
125 out = getoutput("python -c 'print (\"1\")'")
123 self.assertEquals(out.strip(), '1')
126 self.assertEquals(out.strip(), '1')
124
127
125 def test_getoutput(self):
128 def test_getoutput(self):
126 out, err = getoutputerror('python "%s"' % self.fname)
129 out, err = getoutputerror('python "%s"' % self.fname)
127 self.assertEquals(out, 'on stdout')
130 self.assertEquals(out, 'on stdout')
128 self.assertEquals(err, 'on stderr')
131 self.assertEquals(err, 'on stderr')
General Comments 0
You need to be logged in to leave comments. Login now