README.md: title-case Git
[git-cola.git] / cola / core.py
blob4e8f965470aaeeed4583b831b10042c2df958fe1
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.
6 """
7 import os
8 import sys
9 import itertools
10 import platform
11 import subprocess
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)
17 _encoding_tests = [
18 'utf-8',
19 'iso-8859-15',
20 'windows1252',
21 'ascii',
22 # <-- add encodings here
25 def decode(enc, encoding=None):
26 """decode(encoded_string) returns an unencoded unicode string
27 """
28 if enc is None or type(enc) is unicode:
29 return enc
31 if encoding is None:
32 encoding_tests = _encoding_tests
33 else:
34 encoding_tests = itertools.chain([encoding], _encoding_tests)
36 for encoding in encoding_tests:
37 try:
38 return enc.decode(encoding)
39 except:
40 pass
41 # this shouldn't ever happen... FIXME
42 return unicode(enc)
45 def encode(string, encoding=None):
46 """encode(unencoded_string) returns a string encoded in utf-8
47 """
48 if type(string) is not unicode:
49 return string
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)
65 @interruptable
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)
71 @interruptable
72 def fwrite(fh, content, encoding=None):
73 """Write to a filehandle and retry when interrupted"""
74 return fh.write(encode(content, encoding=encoding))
77 @interruptable
78 def wait(proc):
79 """Wait on a subprocess and retry when interrupted"""
80 return proc.wait()
83 @interruptable
84 def readline(fh, encoding=None):
85 return decode(fh.readline(), encoding=encoding)
88 @interruptable
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.
98 """
99 env = None
100 if add_env is not None:
101 env = os.environ.copy()
102 env.update(add_env)
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)
109 @interruptable
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)
130 @interruptable
131 def _fork_posix(args, cwd=None):
132 """Launch a process in the background."""
133 encoded_args = [encode(arg) for arg in args]
134 return subprocess.Popen(encoded_args, cwd=cwd).pid
137 def _fork_win32(args, cwd=None):
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 argv = [encode(arg) for arg in args]
145 abspath = _win32_abspath(argv[0])
146 if abspath:
147 # e.g. fork(['git', 'difftool', '--no-prompt', '--', 'path'])
148 argv[0] = abspath
149 else:
150 # e.g. fork(['gitk', '--all'])
151 cmdstr = subprocess.list2cmdline(argv)
152 sh_exe = _win32_abspath('sh')
153 argv = [sh_exe, '-c', cmdstr]
155 DETACHED_PROCESS = 0x00000008 # Amazing!
156 return subprocess.Popen(argv, cwd=cwd, creationflags=DETACHED_PROCESS).pid
159 def _win32_abspath(exe):
160 """Return the absolute path to an .exe if it exists"""
161 if exists(exe):
162 return exe
163 if not exe.endswith('.exe'):
164 exe += '.exe'
165 if exists(exe):
166 return exe
167 for path in getenv('PATH', '').split(os.pathsep):
168 abspath = os.path.join(path, exe)
169 if exists(abspath):
170 return abspath
171 return None
174 # Portability wrappers
175 if sys.platform == 'win32' or sys.platform == 'cygwin':
176 fork = _fork_win32
177 else:
178 fork = _fork_posix
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)))
187 return wrapped
190 def decorate(decorator, fn):
191 """Decorate the result of `fn` with `action`"""
192 def decorated(*args, **kwargs):
193 return decorator(fn(*args, **kwargs))
194 return decorated
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)
205 def stdout(msg):
206 sys.stdout.write(encode(msg) + '\n')
209 def stderr(msg):
210 sys.stderr.write(encode(msg) + '\n')
213 @interruptable
214 def node():
215 return platform.node()
218 abspath = wrap(encode, os.path.abspath, decorator=decode)
219 chdir = wrap(encode, os.chdir)
220 exists = wrap(encode, os.path.exists)
221 expanduser = wrap(encode, os.path.expanduser, decorator=decode)
222 getcwd = decorate(decode, os.getcwd)
223 isdir = wrap(encode, os.path.isdir)
224 isfile = wrap(encode, os.path.isfile)
225 islink = wrap(encode, os.path.islink)
226 makedirs = wrap(encode, os.makedirs)
227 try:
228 readlink = wrap(encode, os.readlink, decorator=decode)
229 except AttributeError:
230 readlink = lambda p: p
231 realpath = wrap(encode, os.path.realpath, decorator=decode)
232 stat = wrap(encode, os.stat)
233 unlink = wrap(encode, os.unlink)
234 walk = wrap(encode, os.walk)