##// END OF EJS Templates
Allow filename with extension in find_cmd in Windows....
Thomas Kluyver -
Show More
@@ -1,188 +1,188 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 import msvcrt
22 import msvcrt
23
23
24 from ctypes import c_int, POINTER
24 from ctypes import c_int, POINTER
25 from ctypes.wintypes import LPCWSTR, HLOCAL
25 from ctypes.wintypes import LPCWSTR, HLOCAL
26 from subprocess import STDOUT
26 from subprocess import STDOUT
27
27
28 # our own imports
28 # our own imports
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
30 from . import py3compat
30 from . import py3compat
31 from .encoding import DEFAULT_ENCODING
31 from .encoding import DEFAULT_ENCODING
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Function definitions
34 # Function definitions
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class AvoidUNCPath(object):
37 class AvoidUNCPath(object):
38 """A context manager to protect command execution from UNC paths.
38 """A context manager to protect command execution from UNC paths.
39
39
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
41 This context manager temporarily changes directory to the 'C:' drive on
41 This context manager temporarily changes directory to the 'C:' drive on
42 entering, and restores the original working directory on exit.
42 entering, and restores the original working directory on exit.
43
43
44 The context manager returns the starting working directory *if* it made a
44 The context manager returns the starting working directory *if* it made a
45 change and None otherwise, so that users can apply the necessary adjustment
45 change and None otherwise, so that users can apply the necessary adjustment
46 to their system calls in the event of a change.
46 to their system calls in the event of a change.
47
47
48 Example
48 Example
49 -------
49 -------
50 ::
50 ::
51 cmd = 'dir'
51 cmd = 'dir'
52 with AvoidUNCPath() as path:
52 with AvoidUNCPath() as path:
53 if path is not None:
53 if path is not None:
54 cmd = '"pushd %s &&"%s' % (path, cmd)
54 cmd = '"pushd %s &&"%s' % (path, cmd)
55 os.system(cmd)
55 os.system(cmd)
56 """
56 """
57 def __enter__(self):
57 def __enter__(self):
58 self.path = os.getcwdu()
58 self.path = os.getcwdu()
59 self.is_unc_path = self.path.startswith(r"\\")
59 self.is_unc_path = self.path.startswith(r"\\")
60 if self.is_unc_path:
60 if self.is_unc_path:
61 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 # change to c drive (as cmd.exe cannot handle UNC addresses)
62 os.chdir("C:")
62 os.chdir("C:")
63 return self.path
63 return self.path
64 else:
64 else:
65 # We return None to signal that there was no change in the working
65 # We return None to signal that there was no change in the working
66 # directory
66 # directory
67 return None
67 return None
68
68
69 def __exit__(self, exc_type, exc_value, traceback):
69 def __exit__(self, exc_type, exc_value, traceback):
70 if self.is_unc_path:
70 if self.is_unc_path:
71 os.chdir(self.path)
71 os.chdir(self.path)
72
72
73
73
74 def _find_cmd(cmd):
74 def _find_cmd(cmd):
75 """Find the full path to a .bat or .exe using the win32api module."""
75 """Find the full path to a .bat or .exe using the win32api module."""
76 try:
76 try:
77 from win32api import SearchPath
77 from win32api import SearchPath
78 except ImportError:
78 except ImportError:
79 raise ImportError('you need to have pywin32 installed for this to work')
79 raise ImportError('you need to have pywin32 installed for this to work')
80 else:
80 else:
81 PATH = os.environ['PATH']
81 PATH = os.environ['PATH']
82 extensions = ['.exe', '.com', '.bat', '.py']
82 extensions = ['.exe', '.com', '.bat', '.py']
83 path = None
83 path = None
84 for ext in extensions:
84 for ext in extensions:
85 try:
85 try:
86 path = SearchPath(PATH, cmd + ext)[0]
86 path = SearchPath(PATH, cmd, ext)[0]
87 except:
87 except:
88 pass
88 pass
89 if path is None:
89 if path is None:
90 raise OSError("command %r not found" % cmd)
90 raise OSError("command %r not found" % cmd)
91 else:
91 else:
92 return path
92 return path
93
93
94
94
95 def _system_body(p):
95 def _system_body(p):
96 """Callback for _system."""
96 """Callback for _system."""
97 enc = DEFAULT_ENCODING
97 enc = DEFAULT_ENCODING
98 for line in read_no_interrupt(p.stdout).splitlines():
98 for line in read_no_interrupt(p.stdout).splitlines():
99 line = line.decode(enc, 'replace')
99 line = line.decode(enc, 'replace')
100 print(line, file=sys.stdout)
100 print(line, file=sys.stdout)
101 for line in read_no_interrupt(p.stderr).splitlines():
101 for line in read_no_interrupt(p.stderr).splitlines():
102 line = line.decode(enc, 'replace')
102 line = line.decode(enc, 'replace')
103 print(line, file=sys.stderr)
103 print(line, file=sys.stderr)
104
104
105 # Wait to finish for returncode
105 # Wait to finish for returncode
106 return p.wait()
106 return p.wait()
107
107
108
108
109 def system(cmd):
109 def system(cmd):
110 """Win32 version of os.system() that works with network shares.
110 """Win32 version of os.system() that works with network shares.
111
111
112 Note that this implementation returns None, as meant for use in IPython.
112 Note that this implementation returns None, as meant for use in IPython.
113
113
114 Parameters
114 Parameters
115 ----------
115 ----------
116 cmd : str
116 cmd : str
117 A command to be executed in the system shell.
117 A command to be executed in the system shell.
118
118
119 Returns
119 Returns
120 -------
120 -------
121 None : we explicitly do NOT return the subprocess status code, as this
121 None : we explicitly do NOT return the subprocess status code, as this
122 utility is meant to be used extensively in IPython, where any return value
122 utility is meant to be used extensively in IPython, where any return value
123 would trigger :func:`sys.displayhook` calls.
123 would trigger :func:`sys.displayhook` calls.
124 """
124 """
125 # The controller provides interactivity with both
125 # The controller provides interactivity with both
126 # stdin and stdout
126 # stdin and stdout
127 #import _process_win32_controller
127 #import _process_win32_controller
128 #_process_win32_controller.system(cmd)
128 #_process_win32_controller.system(cmd)
129
129
130 with AvoidUNCPath() as path:
130 with AvoidUNCPath() as path:
131 if path is not None:
131 if path is not None:
132 cmd = '"pushd %s &&"%s' % (path, cmd)
132 cmd = '"pushd %s &&"%s' % (path, cmd)
133 return process_handler(cmd, _system_body)
133 return process_handler(cmd, _system_body)
134
134
135 def getoutput(cmd):
135 def getoutput(cmd):
136 """Return standard output of executing cmd in a shell.
136 """Return standard output of executing cmd in a shell.
137
137
138 Accepts the same arguments as os.system().
138 Accepts the same arguments as os.system().
139
139
140 Parameters
140 Parameters
141 ----------
141 ----------
142 cmd : str
142 cmd : str
143 A command to be executed in the system shell.
143 A command to be executed in the system shell.
144
144
145 Returns
145 Returns
146 -------
146 -------
147 stdout : str
147 stdout : str
148 """
148 """
149
149
150 with AvoidUNCPath() as path:
150 with AvoidUNCPath() as path:
151 if path is not None:
151 if path is not None:
152 cmd = '"pushd %s &&"%s' % (path, cmd)
152 cmd = '"pushd %s &&"%s' % (path, cmd)
153 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
153 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
154
154
155 if out is None:
155 if out is None:
156 out = b''
156 out = b''
157 return py3compat.bytes_to_str(out)
157 return py3compat.bytes_to_str(out)
158
158
159 try:
159 try:
160 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
160 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
161 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
161 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
162 CommandLineToArgvW.restype = POINTER(LPCWSTR)
162 CommandLineToArgvW.restype = POINTER(LPCWSTR)
163 LocalFree = ctypes.windll.kernel32.LocalFree
163 LocalFree = ctypes.windll.kernel32.LocalFree
164 LocalFree.res_type = HLOCAL
164 LocalFree.res_type = HLOCAL
165 LocalFree.arg_types = [HLOCAL]
165 LocalFree.arg_types = [HLOCAL]
166
166
167 def arg_split(commandline, posix=False, strict=True):
167 def arg_split(commandline, posix=False, strict=True):
168 """Split a command line's arguments in a shell-like manner.
168 """Split a command line's arguments in a shell-like manner.
169
169
170 This is a special version for windows that use a ctypes call to CommandLineToArgvW
170 This is a special version for windows that use a ctypes call to CommandLineToArgvW
171 to do the argv splitting. The posix paramter is ignored.
171 to do the argv splitting. The posix paramter is ignored.
172
172
173 If strict=False, process_common.arg_split(...strict=False) is used instead.
173 If strict=False, process_common.arg_split(...strict=False) is used instead.
174 """
174 """
175 #CommandLineToArgvW returns path to executable if called with empty string.
175 #CommandLineToArgvW returns path to executable if called with empty string.
176 if commandline.strip() == "":
176 if commandline.strip() == "":
177 return []
177 return []
178 if not strict:
178 if not strict:
179 # not really a cl-arg, fallback on _process_common
179 # not really a cl-arg, fallback on _process_common
180 return py_arg_split(commandline, posix=posix, strict=strict)
180 return py_arg_split(commandline, posix=posix, strict=strict)
181 argvn = c_int()
181 argvn = c_int()
182 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
182 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
183 result_array_type = LPCWSTR * argvn.value
183 result_array_type = LPCWSTR * argvn.value
184 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
184 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
185 retval = LocalFree(result_pointer)
185 retval = LocalFree(result_pointer)
186 return result
186 return result
187 except AttributeError:
187 except AttributeError:
188 arg_split = py_arg_split
188 arg_split = py_arg_split
General Comments 0
You need to be logged in to leave comments. Login now