1 """This module provides core functions for handling unicode and UNIX quirks
3 The @interruptable functions retry when system calls are interrupted,
4 e.g. when python raises an IOError or OSError with errno == EINTR.
7 from __future__
import division
, absolute_import
, unicode_literals
15 from .decorators
import interruptable
16 from .compat
import ustr
17 from .compat
import PY2
18 from .compat
import PY3
19 from .compat
import WIN32
21 # /usr/include/stdlib.h
22 #define EXIT_SUCCESS 0 /* Successful exit status. */
23 #define EXIT_FAILURE 1 /* Failing exit status. */
27 # /usr/include/sysexits.h
28 # #define EX_USAGE 64 /* command line usage error */
29 # #define EX_NOINPUT 66 /* cannot open input */
30 # #define EX_UNAVAILABLE 69 /* service unavailable */
35 # Some files are not in UTF-8; some other aren't in any codification.
36 # Remember that GIT doesn't care about encodings (saves binary data)
42 # <-- add encodings here
46 def decode(enc
, encoding
=None, errors
='strict'):
47 """decode(encoded_string) returns an unencoded unicode string
49 if enc
is None or type(enc
) is ustr
:
53 encoding_tests
= _encoding_tests
55 encoding_tests
= itertools
.chain([encoding
], _encoding_tests
)
57 for encoding
in encoding_tests
:
59 return enc
.decode(encoding
, errors
)
62 return enc
.decode('utf-8', errors
='ignore')
65 def encode(string
, encoding
=None):
66 """encode(unencoded_string) returns a string encoded in utf-8
68 if type(string
) is not ustr
:
70 return string
.encode(encoding
or 'utf-8', 'replace')
73 def mkpath(path
, encoding
=None):
74 # The Windows API requires unicode strings regardless of python version
76 return decode(path
, encoding
=encoding
)
78 return encode(path
, encoding
=encoding
)
81 def list2cmdline(cmd
):
82 return subprocess
.list2cmdline([decode(c
) for c
in cmd
])
85 def read(filename
, size
=-1, encoding
=None, errors
='strict'):
86 """Read filename and return contents"""
87 with
xopen(filename
, 'rb') as fh
:
88 return xread(fh
, size
=size
, encoding
=encoding
, errors
=errors
)
91 def write(path
, contents
, encoding
=None):
92 """Writes a unicode string to a file"""
93 with
xopen(path
, 'wb') as fh
:
94 return xwrite(fh
, contents
, encoding
=encoding
)
98 def xread(fh
, size
=-1, encoding
=None, errors
='strict'):
99 """Read from a filehandle and retry when interrupted"""
100 return decode(fh
.read(size
), encoding
=encoding
, errors
=errors
)
104 def xwrite(fh
, content
, encoding
=None):
105 """Write to a filehandle and retry when interrupted"""
106 return fh
.write(encode(content
, encoding
=encoding
))
111 """Wait on a subprocess and retry when interrupted"""
116 def readline(fh
, encoding
=None):
117 return decode(fh
.readline(), encoding
=encoding
)
121 def start_command(cmd
, cwd
=None, add_env
=None,
122 universal_newlines
=False,
123 stdin
=subprocess
.PIPE
,
124 stdout
=subprocess
.PIPE
,
125 no_win32_startupinfo
=False,
126 stderr
=subprocess
.PIPE
,
128 """Start the given command, and return a subprocess object.
130 This provides a simpler interface to the subprocess module.
133 env
= extra
.pop('env', None)
134 if add_env
is not None:
135 env
= os
.environ
.copy()
138 # Python3 on windows always goes through list2cmdline() internally inside
139 # of subprocess.py so we must provide unicode strings here otherwise
140 # Python3 breaks when bytes are provided.
142 # Additionally, the preferred usage on Python3 is to pass unicode
143 # strings to subprocess. Python will automatically encode into the
144 # default encoding (utf-8) when it gets unicode strings.
145 shell
= extra
.get('shell', False)
146 cmd
= prep_for_subprocess(cmd
, shell
=shell
)
148 if WIN32
and cwd
== getcwd():
149 # Windows cannot deal with passing a cwd that contains unicode
150 # but we luckily can pass None when the supplied cwd is the same
151 # as our current directory and get the same effect.
152 # Not doing this causes unicode encoding errors when launching
160 # If git-cola is invoked on Windows using "start pythonw git-cola",
161 # a console window will briefly flash on the screen each time
162 # git-cola invokes git, which is very annoying. The code below
163 # prevents this by ensuring that any window will be hidden.
164 startupinfo
= subprocess
.STARTUPINFO()
165 startupinfo
.dwFlags
= subprocess
.STARTF_USESHOWWINDOW
166 startupinfo
.wShowWindow
= subprocess
.SW_HIDE
167 extra
['startupinfo'] = startupinfo
169 if WIN32
and not no_win32_startupinfo
:
170 CREATE_NO_WINDOW
= 0x08000000
171 extra
['creationflags'] = CREATE_NO_WINDOW
173 return subprocess
.Popen(cmd
, bufsize
=1, stdin
=stdin
, stdout
=stdout
,
174 stderr
=stderr
, cwd
=cwd
, env
=env
,
175 universal_newlines
=universal_newlines
, **extra
)
178 def prep_for_subprocess(cmd
, shell
=False):
179 """Decode on Python3, encode on Python2"""
180 # See the comment in start_command()
188 cmd
= [decode(c
) for c
in cmd
]
190 cmd
= [encode(c
) for c
in cmd
]
195 def communicate(proc
):
196 return proc
.communicate()
199 def run_command(cmd
, encoding
=None, *args
, **kwargs
):
200 """Run the given command to completion, and return its results.
202 This provides a simpler interface to the subprocess module.
203 The results are formatted as a 3-tuple: (exit_code, output, errors)
204 The other arguments are passed on to start_command().
207 process
= start_command(cmd
, *args
, **kwargs
)
208 (output
, errors
) = communicate(process
)
209 output
= decode(output
, encoding
=encoding
)
210 errors
= decode(errors
, encoding
=encoding
)
211 exit_code
= process
.returncode
212 return (exit_code
, output
or '', errors
or '')
216 def _fork_posix(args
, cwd
=None):
217 """Launch a process in the background."""
218 encoded_args
= [encode(arg
) for arg
in args
]
219 return subprocess
.Popen(encoded_args
, cwd
=cwd
).pid
222 def _fork_win32(args
, cwd
=None):
223 """Launch a background process using crazy win32 voodoo."""
224 # This is probably wrong, but it works. Windows.. wow.
225 if args
[0] == 'git-dag':
226 # win32 can't exec python scripts
227 args
= [sys
.executable
] + args
228 args
[0] = _win32_find_exe(args
[0])
231 # see comment in start_command()
232 argv
= [decode(arg
) for arg
in args
]
234 argv
= [encode(arg
) for arg
in args
]
236 DETACHED_PROCESS
= 0x00000008 # Amazing!
237 return subprocess
.Popen(argv
, cwd
=cwd
, creationflags
=DETACHED_PROCESS
).pid
240 def _win32_find_exe(exe
):
241 """Find the actual file for a Windows executable.
243 This function goes through the same process that the Windows shell uses to
244 locate an executable, taking into account the PATH and PATHEXT environment
245 variables. This allows us to avoid passing shell=True to subprocess.Popen.
248 http://technet.microsoft.com/en-us/library/cc723564.aspx#XSLTsection127121120120
251 # try the argument itself
253 # if argument does not have an extension, also try it with each of the
254 # extensions specified in PATHEXT
256 extensions
= getenv('PATHEXT', '').split(os
.pathsep
)
257 candidates
.extend([(exe
+ ext
) for ext
in extensions
258 if ext
.startswith('.')])
259 # search the current directory first
260 for candidate
in candidates
:
261 if exists(candidate
):
263 # if the argument does not include a path separator, search each of the
264 # directories on the PATH
265 if not os
.path
.dirname(exe
):
266 for path
in getenv('PATH').split(os
.pathsep
):
268 for candidate
in candidates
:
269 full_path
= os
.path
.join(path
, candidate
)
270 if exists(full_path
):
272 # not found, punt and return the argument unchanged
276 # Portability wrappers
277 if sys
.platform
== 'win32' or sys
.platform
== 'cygwin':
283 def _decorator_noop(x
):
287 def wrap(action
, fn
, decorator
=None):
288 """Wrap arguments with `action`, optionally decorate the result"""
289 if decorator
is None:
290 decorator
= _decorator_noop
293 def wrapped(*args
, **kwargs
):
294 return decorator(fn(action(*args
, **kwargs
)))
299 def decorate(decorator
, fn
):
300 """Decorate the result of `fn` with `action`"""
302 def decorated(*args
, **kwargs
):
303 return decorator(fn(*args
, **kwargs
))
307 def getenv(name
, default
=None):
308 return decode(os
.getenv(name
, default
))
311 def xopen(path
, mode
='r', encoding
=None):
312 return open(mkpath(path
, encoding
=encoding
), mode
)
315 def stdout(msg
, linesep
='\n'):
318 msg
= encode(msg
, encoding
='utf-8')
319 sys
.stdout
.write(msg
)
322 def stderr(msg
, linesep
='\n'):
325 msg
= encode(msg
, encoding
='utf-8')
326 sys
.stderr
.write(msg
)
329 def error(msg
, status
=EXIT_FAILURE
, linesep
='\n'):
330 stderr(msg
, linesep
=linesep
)
336 return platform
.node()
339 abspath
= wrap(mkpath
, os
.path
.abspath
, decorator
=decode
)
340 chdir
= wrap(mkpath
, os
.chdir
)
341 exists
= wrap(mkpath
, os
.path
.exists
)
342 expanduser
= wrap(encode
, os
.path
.expanduser
, decorator
=decode
)
344 getcwd
= decorate(decode
, os
.getcwd
)
347 isdir
= wrap(mkpath
, os
.path
.isdir
)
348 isfile
= wrap(mkpath
, os
.path
.isfile
)
349 islink
= wrap(mkpath
, os
.path
.islink
)
350 makedirs
= wrap(mkpath
, os
.makedirs
)
352 readlink
= wrap(mkpath
, os
.readlink
, decorator
=decode
)
353 except AttributeError:
355 def _readlink_noop(p
):
358 readlink
= _readlink_noop
360 realpath
= wrap(mkpath
, os
.path
.realpath
, decorator
=decode
)
361 relpath
= wrap(mkpath
, os
.path
.relpath
, decorator
=decode
)
362 stat
= wrap(mkpath
, os
.stat
)
363 unlink
= wrap(mkpath
, os
.unlink
)
364 walk
= wrap(mkpath
, os
.walk
)