##// END OF EJS Templates
Allow process_handler to accept a list of arguments
Thomas Kluyver -
Show More
@@ -1,214 +1,215 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 shlex
19 import sys
19 import sys
20
20
21 from IPython.utils import py3compat
21 from IPython.utils import py3compat
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Function definitions
24 # Function definitions
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 def read_no_interrupt(p):
27 def read_no_interrupt(p):
28 """Read from a pipe ignoring EINTR errors.
28 """Read from a pipe ignoring EINTR errors.
29
29
30 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
31 running in the background, often interrupts are raised that stop the
31 running in the background, often interrupts are raised that stop the
32 command from completing."""
32 command from completing."""
33 import errno
33 import errno
34
34
35 try:
35 try:
36 return p.read()
36 return p.read()
37 except IOError as err:
37 except IOError as err:
38 if err.errno != errno.EINTR:
38 if err.errno != errno.EINTR:
39 raise
39 raise
40
40
41
41
42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
43 """Open a command in a shell subprocess and execute a callback.
43 """Open a command in a shell subprocess and execute a callback.
44
44
45 This function provides common scaffolding for creating subprocess.Popen()
45 This function provides common scaffolding for creating subprocess.Popen()
46 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.
47
47
48 Parameters
48 Parameters
49 ----------
49 ----------
50 cmd : str
50 cmd : str or list
51 A string to be executed with the underlying system shell (by calling
51 A command to be executed by the system, using :class:`subprocess.Popen`.
52 :func:`Popen` with ``shell=True``.
52 If a string is passed, it will be run in the system shell. If a list is
53 passed, it will be used directly as arguments.
53
54
54 callback : callable
55 callback : callable
55 A one-argument function that will be called with the Popen object.
56 A one-argument function that will be called with the Popen object.
56
57
57 stderr : file descriptor number, optional
58 stderr : file descriptor number, optional
58 By default this is set to ``subprocess.PIPE``, but you can also pass the
59 By default this is set to ``subprocess.PIPE``, but you can also pass the
59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
60 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
60 the same file descriptor as its stdout. This is useful to read stdout
61 the same file descriptor as its stdout. This is useful to read stdout
61 and stderr combined in the order they are generated.
62 and stderr combined in the order they are generated.
62
63
63 Returns
64 Returns
64 -------
65 -------
65 The return value of the provided callback is returned.
66 The return value of the provided callback is returned.
66 """
67 """
67 sys.stdout.flush()
68 sys.stdout.flush()
68 sys.stderr.flush()
69 sys.stderr.flush()
69 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 close_fds = sys.platform != 'win32'
71 close_fds = sys.platform != 'win32'
71 p = subprocess.Popen(cmd, shell=True,
72 p = subprocess.Popen(cmd, shell=isinstance(cmd, py3compat.string_types),
72 stdin=subprocess.PIPE,
73 stdin=subprocess.PIPE,
73 stdout=subprocess.PIPE,
74 stdout=subprocess.PIPE,
74 stderr=stderr,
75 stderr=stderr,
75 close_fds=close_fds)
76 close_fds=close_fds)
76
77
77 try:
78 try:
78 out = callback(p)
79 out = callback(p)
79 except KeyboardInterrupt:
80 except KeyboardInterrupt:
80 print('^C')
81 print('^C')
81 sys.stdout.flush()
82 sys.stdout.flush()
82 sys.stderr.flush()
83 sys.stderr.flush()
83 out = None
84 out = None
84 finally:
85 finally:
85 # Make really sure that we don't leave processes behind, in case the
86 # Make really sure that we don't leave processes behind, in case the
86 # call above raises an exception
87 # call above raises an exception
87 # We start by assuming the subprocess finished (to avoid NameErrors
88 # We start by assuming the subprocess finished (to avoid NameErrors
88 # later depending on the path taken)
89 # later depending on the path taken)
89 if p.returncode is None:
90 if p.returncode is None:
90 try:
91 try:
91 p.terminate()
92 p.terminate()
92 p.poll()
93 p.poll()
93 except OSError:
94 except OSError:
94 pass
95 pass
95 # One last try on our way out
96 # One last try on our way out
96 if p.returncode is None:
97 if p.returncode is None:
97 try:
98 try:
98 p.kill()
99 p.kill()
99 except OSError:
100 except OSError:
100 pass
101 pass
101
102
102 return out
103 return out
103
104
104
105
105 def getoutput(cmd):
106 def getoutput(cmd):
106 """Run a command and return its stdout/stderr as a string.
107 """Run a command and return its stdout/stderr as a string.
107
108
108 Parameters
109 Parameters
109 ----------
110 ----------
110 cmd : str
111 cmd : str or list
111 A command to be executed in the system shell.
112 A command to be executed in the system shell.
112
113
113 Returns
114 Returns
114 -------
115 -------
115 output : str
116 output : str
116 A string containing the combination of stdout and stderr from the
117 A string containing the combination of stdout and stderr from the
117 subprocess, in whatever order the subprocess originally wrote to its
118 subprocess, in whatever order the subprocess originally wrote to its
118 file descriptors (so the order of the information in this string is the
119 file descriptors (so the order of the information in this string is the
119 correct order as would be seen if running the command in a terminal).
120 correct order as would be seen if running the command in a terminal).
120 """
121 """
121 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
122 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
122 if out is None:
123 if out is None:
123 return ''
124 return ''
124 return py3compat.bytes_to_str(out)
125 return py3compat.bytes_to_str(out)
125
126
126
127
127 def getoutputerror(cmd):
128 def getoutputerror(cmd):
128 """Return (standard output, standard error) of executing cmd in a shell.
129 """Return (standard output, standard error) of executing cmd in a shell.
129
130
130 Accepts the same arguments as os.system().
131 Accepts the same arguments as os.system().
131
132
132 Parameters
133 Parameters
133 ----------
134 ----------
134 cmd : str
135 cmd : str or list
135 A command to be executed in the system shell.
136 A command to be executed in the system shell.
136
137
137 Returns
138 Returns
138 -------
139 -------
139 stdout : str
140 stdout : str
140 stderr : str
141 stderr : str
141 """
142 """
142 return get_output_error_code(cmd)[:2]
143 return get_output_error_code(cmd)[:2]
143
144
144 def get_output_error_code(cmd):
145 def get_output_error_code(cmd):
145 """Return (standard output, standard error, return code) of executing cmd
146 """Return (standard output, standard error, return code) of executing cmd
146 in a shell.
147 in a shell.
147
148
148 Accepts the same arguments as os.system().
149 Accepts the same arguments as os.system().
149
150
150 Parameters
151 Parameters
151 ----------
152 ----------
152 cmd : str
153 cmd : str or list
153 A command to be executed in the system shell.
154 A command to be executed in the system shell.
154
155
155 Returns
156 Returns
156 -------
157 -------
157 stdout : str
158 stdout : str
158 stderr : str
159 stderr : str
159 returncode: int
160 returncode: int
160 """
161 """
161
162
162 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
163 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
163 if out_err is None:
164 if out_err is None:
164 return '', '', p.returncode
165 return '', '', p.returncode
165 out, err = out_err
166 out, err = out_err
166 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err), p.returncode
167 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err), p.returncode
167
168
168 def arg_split(s, posix=False, strict=True):
169 def arg_split(s, posix=False, strict=True):
169 """Split a command line's arguments in a shell-like manner.
170 """Split a command line's arguments in a shell-like manner.
170
171
171 This is a modified version of the standard library's shlex.split()
172 This is a modified version of the standard library's shlex.split()
172 function, but with a default of posix=False for splitting, so that quotes
173 function, but with a default of posix=False for splitting, so that quotes
173 in inputs are respected.
174 in inputs are respected.
174
175
175 if strict=False, then any errors shlex.split would raise will result in the
176 if strict=False, then any errors shlex.split would raise will result in the
176 unparsed remainder being the last element of the list, rather than raising.
177 unparsed remainder being the last element of the list, rather than raising.
177 This is because we sometimes use arg_split to parse things other than
178 This is because we sometimes use arg_split to parse things other than
178 command-line args.
179 command-line args.
179 """
180 """
180
181
181 # Unfortunately, python's shlex module is buggy with unicode input:
182 # Unfortunately, python's shlex module is buggy with unicode input:
182 # http://bugs.python.org/issue1170
183 # http://bugs.python.org/issue1170
183 # At least encoding the input when it's unicode seems to help, but there
184 # At least encoding the input when it's unicode seems to help, but there
184 # may be more problems lurking. Apparently this is fixed in python3.
185 # may be more problems lurking. Apparently this is fixed in python3.
185 is_unicode = False
186 is_unicode = False
186 if (not py3compat.PY3) and isinstance(s, unicode):
187 if (not py3compat.PY3) and isinstance(s, unicode):
187 is_unicode = True
188 is_unicode = True
188 s = s.encode('utf-8')
189 s = s.encode('utf-8')
189 lex = shlex.shlex(s, posix=posix)
190 lex = shlex.shlex(s, posix=posix)
190 lex.whitespace_split = True
191 lex.whitespace_split = True
191 # Extract tokens, ensuring that things like leaving open quotes
192 # Extract tokens, ensuring that things like leaving open quotes
192 # does not cause this to raise. This is important, because we
193 # does not cause this to raise. This is important, because we
193 # sometimes pass Python source through this (e.g. %timeit f(" ")),
194 # sometimes pass Python source through this (e.g. %timeit f(" ")),
194 # and it shouldn't raise an exception.
195 # and it shouldn't raise an exception.
195 # It may be a bad idea to parse things that are not command-line args
196 # It may be a bad idea to parse things that are not command-line args
196 # through this function, but we do, so let's be safe about it.
197 # through this function, but we do, so let's be safe about it.
197 lex.commenters='' #fix for GH-1269
198 lex.commenters='' #fix for GH-1269
198 tokens = []
199 tokens = []
199 while True:
200 while True:
200 try:
201 try:
201 tokens.append(next(lex))
202 tokens.append(next(lex))
202 except StopIteration:
203 except StopIteration:
203 break
204 break
204 except ValueError:
205 except ValueError:
205 if strict:
206 if strict:
206 raise
207 raise
207 # couldn't parse, get remaining blob as last token
208 # couldn't parse, get remaining blob as last token
208 tokens.append(lex.token)
209 tokens.append(lex.token)
209 break
210 break
210
211
211 if is_unicode:
212 if is_unicode:
212 # Convert the tokens back to unicode.
213 # Convert the tokens back to unicode.
213 tokens = [x.decode('utf-8') for x in tokens]
214 tokens = [x.decode('utf-8') for x in tokens]
214 return tokens
215 return tokens
@@ -1,187 +1,187 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, arg_split as py_arg_split
28 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 from . import py3compat
29 from . import py3compat
30 from .encoding import DEFAULT_ENCODING
30 from .encoding import DEFAULT_ENCODING
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 Examples
47 Examples
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 = py3compat.getcwd()
57 self.path = py3compat.getcwd()
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 = DEFAULT_ENCODING
96 enc = DEFAULT_ENCODING
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 or list
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 # The controller provides interactivity with both
124 # The controller provides interactivity with both
125 # stdin and stdout
125 # stdin and stdout
126 #import _process_win32_controller
126 #import _process_win32_controller
127 #_process_win32_controller.system(cmd)
127 #_process_win32_controller.system(cmd)
128
128
129 with AvoidUNCPath() as path:
129 with AvoidUNCPath() as path:
130 if path is not None:
130 if path is not None:
131 cmd = '"pushd %s &&"%s' % (path, cmd)
131 cmd = '"pushd %s &&"%s' % (path, cmd)
132 return process_handler(cmd, _system_body)
132 return process_handler(cmd, _system_body)
133
133
134 def getoutput(cmd):
134 def getoutput(cmd):
135 """Return standard output of executing cmd in a shell.
135 """Return standard output of executing cmd in a shell.
136
136
137 Accepts the same arguments as os.system().
137 Accepts the same arguments as os.system().
138
138
139 Parameters
139 Parameters
140 ----------
140 ----------
141 cmd : str
141 cmd : str or list
142 A command to be executed in the system shell.
142 A command to be executed in the system shell.
143
143
144 Returns
144 Returns
145 -------
145 -------
146 stdout : str
146 stdout : str
147 """
147 """
148
148
149 with AvoidUNCPath() as path:
149 with AvoidUNCPath() as path:
150 if path is not None:
150 if path is not None:
151 cmd = '"pushd %s &&"%s' % (path, cmd)
151 cmd = '"pushd %s &&"%s' % (path, cmd)
152 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
152 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
153
153
154 if out is None:
154 if out is None:
155 out = b''
155 out = b''
156 return py3compat.bytes_to_str(out)
156 return py3compat.bytes_to_str(out)
157
157
158 try:
158 try:
159 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
159 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
160 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
160 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
161 CommandLineToArgvW.restype = POINTER(LPCWSTR)
161 CommandLineToArgvW.restype = POINTER(LPCWSTR)
162 LocalFree = ctypes.windll.kernel32.LocalFree
162 LocalFree = ctypes.windll.kernel32.LocalFree
163 LocalFree.res_type = HLOCAL
163 LocalFree.res_type = HLOCAL
164 LocalFree.arg_types = [HLOCAL]
164 LocalFree.arg_types = [HLOCAL]
165
165
166 def arg_split(commandline, posix=False, strict=True):
166 def arg_split(commandline, posix=False, strict=True):
167 """Split a command line's arguments in a shell-like manner.
167 """Split a command line's arguments in a shell-like manner.
168
168
169 This is a special version for windows that use a ctypes call to CommandLineToArgvW
169 This is a special version for windows that use a ctypes call to CommandLineToArgvW
170 to do the argv splitting. The posix paramter is ignored.
170 to do the argv splitting. The posix paramter is ignored.
171
171
172 If strict=False, process_common.arg_split(...strict=False) is used instead.
172 If strict=False, process_common.arg_split(...strict=False) is used instead.
173 """
173 """
174 #CommandLineToArgvW returns path to executable if called with empty string.
174 #CommandLineToArgvW returns path to executable if called with empty string.
175 if commandline.strip() == "":
175 if commandline.strip() == "":
176 return []
176 return []
177 if not strict:
177 if not strict:
178 # not really a cl-arg, fallback on _process_common
178 # not really a cl-arg, fallback on _process_common
179 return py_arg_split(commandline, posix=posix, strict=strict)
179 return py_arg_split(commandline, posix=posix, strict=strict)
180 argvn = c_int()
180 argvn = c_int()
181 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
181 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
182 result_array_type = LPCWSTR * argvn.value
182 result_array_type = LPCWSTR * argvn.value
183 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
183 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
184 retval = LocalFree(result_pointer)
184 retval = LocalFree(result_pointer)
185 return result
185 return result
186 except AttributeError:
186 except AttributeError:
187 arg_split = py_arg_split
187 arg_split = py_arg_split
General Comments 0
You need to be logged in to leave comments. Login now