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.
13 from cola
.decorators
import interruptable
15 # Some files are not in UTF-8; some other aren't in any codification.
16 # Remember that GIT doesn't care about encodings (saves binary data)
22 # <-- add encodings here
25 def decode(enc
, encoding
=None):
26 """decode(encoded_string) returns an unencoded unicode string
28 if enc
is None or type(enc
) is unicode:
32 encoding_tests
= _encoding_tests
34 encoding_tests
= itertools
.chain([encoding
], _encoding_tests
)
36 for encoding
in encoding_tests
:
38 return enc
.decode(encoding
)
41 # this shouldn't ever happen... FIXME
45 def encode(string
, encoding
=None):
46 """encode(unencoded_string) returns a string encoded in utf-8
48 if type(string
) is not unicode:
50 return string
.encode(encoding
or 'utf-8', 'replace')
53 def read(filename
, size
=-1, encoding
=None):
54 """Read filename and return contents"""
55 with
xopen(filename
, 'r') as fh
:
56 return fread(fh
, size
=size
, encoding
=encoding
)
59 def write(path
, contents
, encoding
=None):
60 """Writes a unicode string to a file"""
61 with
xopen(path
, 'wb') as fh
:
62 return fwrite(fh
, contents
, encoding
=encoding
)
66 def fread(fh
, size
=-1, encoding
=None):
67 """Read from a filehandle and retry when interrupted"""
68 return decode(fh
.read(size
), encoding
=encoding
)
72 def fwrite(fh
, content
, encoding
=None):
73 """Write to a filehandle and retry when interrupted"""
74 return fh
.write(encode(content
, encoding
=encoding
))
79 """Wait on a subprocess and retry when interrupted"""
84 def readline(fh
, encoding
=None):
85 return decode(fh
.readline(), encoding
=encoding
)
89 def start_command(cmd
, cwd
=None, shell
=False, add_env
=None,
90 universal_newlines
=False,
91 stdin
=subprocess
.PIPE
,
92 stdout
=subprocess
.PIPE
,
93 stderr
=subprocess
.PIPE
):
94 """Start the given command, and return a subprocess object.
96 This provides a simpler interface to the subprocess module.
100 if add_env
is not None:
101 env
= os
.environ
.copy()
103 cmd
= [encode(c
) for c
in cmd
]
104 return subprocess
.Popen(cmd
, bufsize
=1, stdin
=stdin
, stdout
=stdout
,
105 stderr
=stderr
, cwd
=cwd
, shell
=shell
, env
=env
,
106 universal_newlines
=universal_newlines
)
110 def communicate(proc
):
111 return proc
.communicate()
114 def run_command(cmd
, encoding
=None, *args
, **kwargs
):
115 """Run the given command to completion, and return its results.
117 This provides a simpler interface to the subprocess module.
118 The results are formatted as a 3-tuple: (exit_code, output, errors)
119 The other arguments are passed on to start_command().
122 process
= start_command(cmd
, *args
, **kwargs
)
123 (output
, errors
) = communicate(process
)
124 output
= decode(output
, encoding
=encoding
)
125 errors
= decode(errors
, encoding
=encoding
)
126 exit_code
= process
.returncode
127 return (exit_code
, output
, errors
)
131 def _fork_posix(args
):
132 """Launch a process in the background."""
133 encoded_args
= [encode(arg
) for arg
in args
]
134 return subprocess
.Popen(encoded_args
).pid
137 def _fork_win32(args
):
138 """Launch a background process using crazy win32 voodoo."""
139 # This is probably wrong, but it works. Windows.. wow.
140 if args
[0] == 'git-dag':
141 # win32 can't exec python scripts
142 args
= [sys
.executable
] + args
144 enc_args
= [encode(arg
) for arg
in args
]
145 abspath
= _win32_abspath(enc_args
[0])
147 # e.g. fork(['git', 'difftool', '--no-prompt', '--', 'path'])
148 enc_args
[0] = abspath
150 # e.g. fork(['gitk', '--all'])
151 cmdstr
= subprocess
.list2cmdline(enc_args
)
152 sh_exe
= _win32_abspath('sh')
153 enc_args
= [sh_exe
, '-c', cmdstr
]
155 DETACHED_PROCESS
= 0x00000008 # Amazing!
156 return subprocess
.Popen(enc_args
, creationflags
=DETACHED_PROCESS
).pid
159 def _win32_abspath(exe
):
160 """Return the absolute path to an .exe if it exists"""
163 if not exe
.endswith('.exe'):
167 for path
in getenv('PATH', '').split(os
.pathsep
):
168 abspath
= os
.path
.join(path
, exe
)
174 # Portability wrappers
175 if sys
.platform
== 'win32' or sys
.platform
== 'cygwin':
181 def wrap(action
, fn
, decorator
=None):
182 """Wrap arguments with `action`, optionally decorate the result"""
183 if decorator
is None:
184 decorator
= lambda x
: x
185 def wrapped(*args
, **kwargs
):
186 return decorator(fn(action(*args
, **kwargs
)))
190 def decorate(decorator
, fn
):
191 """Decorate the result of `fn` with `action`"""
192 def decorated(*args
, **kwargs
):
193 return decorator(fn(*args
, **kwargs
))
197 def getenv(name
, default
=None):
198 return decode(os
.getenv(encode(name
), default
))
201 def xopen(path
, mode
='r', encoding
=None):
202 return open(encode(path
, encoding
=encoding
), mode
)
206 sys
.stdout
.write(encode(msg
) + '\n')
210 sys
.stderr
.write(encode(msg
) + '\n')
215 return platform
.node()
218 abspath
= wrap(encode
, os
.path
.abspath
, decorator
=decode
)
219 exists
= wrap(encode
, os
.path
.exists
)
220 expanduser
= wrap(encode
, os
.path
.expanduser
, decorator
=decode
)
221 getcwd
= decorate(decode
, os
.getcwd
)
222 isdir
= wrap(encode
, os
.path
.isdir
)
223 isfile
= wrap(encode
, os
.path
.isfile
)
224 islink
= wrap(encode
, os
.path
.islink
)
225 makedirs
= wrap(encode
, os
.makedirs
)
227 readlink
= wrap(encode
, os
.readlink
, decorator
=decode
)
228 except AttributeError:
229 readlink
= lambda p
: p
230 realpath
= wrap(encode
, os
.path
.realpath
, decorator
=decode
)
231 stat
= wrap(encode
, os
.stat
)
232 unlink
= wrap(encode
, os
.unlink
)
233 walk
= wrap(encode
, os
.walk
)