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.
12 from cola
.decorators
import interruptable
14 # Some files are not in UTF-8; some other aren't in any codification.
15 # Remember that GIT doesn't care about encodings (saves binary data)
21 # <-- add encodings here
24 def decode(enc
, encoding
=None):
25 """decode(encoded_string) returns an unencoded unicode string
27 if type(enc
) is unicode:
31 encoding_tests
= _encoding_tests
33 encoding_tests
= itertools
.chain([encoding
], _encoding_tests
)
35 for encoding
in encoding_tests
:
37 return enc
.decode(encoding
)
40 # this shouldn't ever happen... FIXME
44 def encode(string
, encoding
=None):
45 """encode(unencoded_string) returns a string encoded in utf-8
47 if type(string
) is not unicode:
49 return string
.encode(encoding
or 'utf-8', 'replace')
52 def read(filename
, size
=-1, encoding
=None):
53 """Read filename and return contents"""
54 with
xopen(filename
, 'r') as fh
:
55 return fread(fh
, size
=size
, encoding
=encoding
)
58 def write(path
, contents
, encoding
=None):
59 """Writes a unicode string to a file"""
60 with
xopen(path
, 'wb') as fh
:
61 return fwrite(fh
, contents
, encoding
=encoding
)
65 def fread(fh
, size
=-1, encoding
=None):
66 """Read from a filehandle and retry when interrupted"""
67 return decode(fh
.read(size
), encoding
=encoding
)
71 def fwrite(fh
, content
, encoding
=None):
72 """Write to a filehandle and retry when interrupted"""
73 return fh
.write(encode(content
, encoding
=encoding
))
78 """Wait on a subprocess and retry when interrupted"""
83 def readline(fh
, encoding
=None):
84 return decode(fh
.readline(), encoding
=encoding
)
88 def start_command(cmd
, cwd
=None, shell
=False, add_env
=None,
89 universal_newlines
=False,
90 stdin
=subprocess
.PIPE
,
91 stdout
=subprocess
.PIPE
,
92 stderr
=subprocess
.PIPE
):
93 """Start the given command, and return a subprocess object.
95 This provides a simpler interface to the subprocess module.
99 if add_env
is not None:
100 env
= os
.environ
.copy()
102 return subprocess
.Popen(cmd
, bufsize
=1, stdin
=stdin
, stdout
=stdout
,
103 stderr
=stderr
, cwd
=cwd
, shell
=shell
, env
=env
,
104 universal_newlines
=universal_newlines
)
107 def run_command(cmd
, *args
, **kwargs
):
108 """Run the given command to completion, and return its results.
110 This provides a simpler interface to the subprocess module.
111 The results are formatted as a 3-tuple: (exit_code, output, errors)
112 The other arguments are passed on to start_command().
115 process
= start_command(cmd
, *args
, **kwargs
)
116 (output
, errors
) = process
.communicate()
117 output
= decode(output
)
118 errors
= decode(errors
)
119 exit_code
= process
.returncode
120 return (exit_code
, output
, errors
)
124 def _fork_posix(args
):
125 """Launch a process in the background."""
126 encoded_args
= [encode(arg
) for arg
in args
]
127 return subprocess
.Popen(encoded_args
).pid
130 def _fork_win32(args
):
131 """Launch a background process using crazy win32 voodoo."""
132 # This is probably wrong, but it works. Windows.. wow.
133 if args
[0] == 'git-dag':
134 # win32 can't exec python scripts
135 args
= [sys
.executable
] + args
137 enc_args
= [encode(arg
) for arg
in args
]
138 abspath
= _win32_abspath(enc_args
[0])
140 # e.g. fork(['git', 'difftool', '--no-prompt', '--', 'path'])
141 enc_args
[0] = abspath
143 # e.g. fork(['gitk', '--all'])
144 cmdstr
= subprocess
.list2cmdline(enc_args
)
145 sh_exe
= _win32_abspath('sh')
146 enc_args
= [sh_exe
, '-c', cmdstr
]
148 DETACHED_PROCESS
= 0x00000008 # Amazing!
149 return subprocess
.Popen(enc_args
, creationflags
=DETACHED_PROCESS
).pid
152 def _win32_abspath(exe
):
153 """Return the absolute path to an .exe if it exists"""
156 if not exe
.endswith('.exe'):
160 for path
in getenv('PATH', '').split(os
.pathsep
):
161 abspath
= os
.path
.join(path
, exe
)
167 # Portability wrappers
168 if sys
.platform
== 'win32' or sys
.platform
== 'cygwin':
174 def wrap(action
, fn
, decorator
=None):
175 """Wrap arguments with `action`, optionally decorate the result"""
176 if decorator
is None:
177 decorator
= lambda x
: x
178 def wrapped(*args
, **kwargs
):
179 return decorator(fn(action(*args
, **kwargs
)))
183 def decorate(decorator
, fn
):
184 """Decorate the result of `fn` with `action`"""
185 def decorated(*args
, **kwargs
):
186 return decorator(fn(*args
, **kwargs
))
190 def getenv(name
, default
=None):
191 return decode(os
.getenv(encode(name
), default
))
194 def xopen(path
, mode
='r', encoding
=None):
195 return open(encode(path
, encoding
=encoding
), mode
)
199 sys
.stdout
.write(encode(msg
) + '\n')
203 sys
.stderr
.write(encode(msg
) + '\n')
206 abspath
= wrap(encode
, os
.path
.abspath
, decorator
=decode
)
207 exists
= wrap(encode
, os
.path
.exists
)
208 expanduser
= wrap(encode
, os
.path
.expanduser
, decorator
=decode
)
209 getcwd
= decorate(decode
, os
.getcwd
)
210 isdir
= wrap(encode
, os
.path
.isdir
)
211 isfile
= wrap(encode
, os
.path
.isfile
)
212 islink
= wrap(encode
, os
.path
.islink
)
213 makedirs
= wrap(encode
, os
.makedirs
)
215 readlink
= wrap(encode
, os
.readlink
, decorator
=decode
)
216 except AttributeError:
217 readlink
= lambda p
: p
218 realpath
= wrap(encode
, os
.path
.realpath
, decorator
=decode
)
219 stat
= wrap(encode
, os
.stat
)
220 unlink
= wrap(encode
, os
.unlink
)