doc: Document the cola.qt module
[git-cola.git] / cola / utils.py
blob3d8d8c5e0e996e387260bcd3724dd48577ae62c7
1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous utility functions."""
4 import os
5 import re
6 import sys
7 import errno
8 import platform
9 import subprocess
10 import hashlib
11 import mimetypes
13 from glob import glob
14 from cStringIO import StringIO
16 from cola import git
17 from cola import core
18 from cola import resources
19 from cola.git import shell_quote
21 KNOWN_FILE_MIME_TYPES = {
22 'text': 'script.png',
23 'image': 'image.png',
24 'python': 'script.png',
25 'ruby': 'script.png',
26 'shell': 'script.png',
27 'perl': 'script.png',
28 'octet': 'binary.png',
31 KNOWN_FILE_EXTENSION = {
32 '.java': 'script.png',
33 '.groovy': 'script.png',
34 '.cpp': 'script.png',
35 '.c': 'script.png',
36 '.h': 'script.png',
37 '.cxx': 'script.png',
41 def add_parents(path_entry_set):
42 """Iterate over each item in the set and add its parent directories."""
43 for path in list(path_entry_set):
44 while '//' in path:
45 path = path.replace('//', '/')
46 if path not in path_entry_set:
47 path_entry_set.add(path)
48 if '/' in path:
49 parent_dir = dirname(path)
50 while parent_dir and parent_dir not in path_entry_set:
51 path_entry_set.add(parent_dir)
52 parent_dir = dirname(parent_dir)
53 return path_entry_set
56 def run_cmd(command):
57 """
58 Run arguments as a command and return output.
60 >>> run_cmd(["echo", "hello", "world"])
61 'hello world'
63 """
64 return git.Git.execute(command)
67 def ident_file_type(filename):
68 """Returns an icon based on the contents of filename."""
69 if os.path.exists(filename):
70 filemimetype = mimetypes.guess_type(filename)
71 if filemimetype[0] != None:
72 for filetype, iconname in KNOWN_FILE_MIME_TYPES.iteritems():
73 if filetype in filemimetype[0].lower():
74 return iconname
75 filename = filename.lower()
76 for fileext, iconname in KNOWN_FILE_EXTENSION.iteritems():
77 if filename.endswith(fileext):
78 return iconname
79 return 'generic.png'
80 else:
81 return 'removed.png'
82 # Fallback for modified files of an unknown type
83 return 'generic.png'
86 def file_icon(filename):
87 """
88 Returns the full path to an icon file corresponding to
89 filename"s contents.
90 """
91 return resources.icon(ident_file_type(filename))
94 def win32_abspath(exe):
95 """Return the absolute path to an .exe if it exists"""
96 if os.path.exists(exe):
97 return exe
98 if not exe.endswith('.exe'):
99 exe += '.exe'
100 if os.path.exists(exe):
101 return exe
102 for path in os.environ['PATH'].split(os.pathsep):
103 abspath = os.path.join(path, exe)
104 if os.path.exists(abspath):
105 return abspath
106 return None
109 def win32_expand_paths(args):
110 """Expand filenames after the double-dash"""
111 if '--' not in args:
112 return args
113 dashes_idx = args.index('--')
114 cmd = args[:dashes_idx+1]
115 for path in args[dashes_idx+1:]:
116 cmd.append(shell_quote(os.path.join(os.getcwd(), path)))
117 return cmd
120 def fork(args):
121 """Launch a command in the background."""
122 if is_win32():
123 # Windows is absolutely insane.
125 # If we want to launch 'gitk' we have to use the 'sh -c' trick.
127 # If we want to launch 'git.exe' we have to expand all filenames
128 # after the double-dash.
130 # os.spawnv wants an absolute path in the command name but not in
131 # the command vector. Wow.
132 enc_args = win32_expand_paths([core.encode(a) for a in args])
133 abspath = win32_abspath(enc_args[0])
134 if abspath:
135 # e.g. fork(['git', 'difftool', '--no-prompt', '--', 'path'])
136 return os.spawnv(os.P_NOWAIT, abspath, enc_args)
138 # e.g. fork(['gitk', '--all'])
139 sh_exe = win32_abspath('sh')
140 enc_argv = map(shell_quote, enc_args)
141 cmdstr = ' '.join(enc_argv)
142 cmd = ['sh.exe', '-c', cmdstr]
143 return os.spawnv(os.P_NOWAIT, sh_exe, cmd)
144 else:
145 # Unix is absolutely simple
146 enc_args = [core.encode(a) for a in args]
147 enc_argv = map(shell_quote, enc_args)
148 cmdstr = ' '.join(enc_argv)
149 return os.system(cmdstr + '&')
152 def sublist(a,b):
153 """Subtracts list b from list a and returns the resulting list."""
154 # conceptually, c = a - b
155 c = []
156 for item in a:
157 if item not in b:
158 c.append(item)
159 return c
162 __grep_cache = {}
163 def grep(pattern, items, squash=True):
164 """Greps a list for items that match a pattern and return a list of
165 matching items. If only one item matches, return just that item.
167 isdict = type(items) is dict
168 if pattern in __grep_cache:
169 regex = __grep_cache[pattern]
170 else:
171 regex = __grep_cache[pattern] = re.compile(pattern)
172 matched = []
173 matchdict = {}
174 for item in items:
175 match = regex.match(item)
176 if not match:
177 continue
178 groups = match.groups()
179 if not groups:
180 subitems = match.group(0)
181 else:
182 if len(groups) == 1:
183 subitems = groups[0]
184 else:
185 subitems = list(groups)
186 if isdict:
187 matchdict[item] = items[item]
188 else:
189 matched.append(subitems)
191 if isdict:
192 return matchdict
193 else:
194 if squash and len(matched) == 1:
195 return matched[0]
196 else:
197 return matched
200 def basename(path):
202 An os.path.basename() implementation that always uses '/'
204 Avoid os.path.basename because git's output always
205 uses '/' regardless of platform.
208 return path.rsplit('/', 1)[-1]
211 def dirname(path):
213 An os.path.dirname() implementation that always uses '/'
215 Avoid os.path.dirname because git's output always
216 uses '/' regardless of platform.
219 while '//' in path:
220 path = path.replace('//', '/')
221 path_dirname = path.rsplit('/', 1)[0]
222 if path_dirname == path:
223 return ''
224 return path.rsplit('/', 1)[0]
227 def slurp(path):
228 """Slurps a filepath into a string."""
229 fh = open(path)
230 slushy = core.read_nointr(fh)
231 fh.close()
232 return core.decode(slushy)
235 def write(path, contents):
236 """Writes a string to a file."""
237 fh = open(path, 'w')
238 core.write_nointr(fh, core.encode(contents))
239 fh.close()
241 def strip_prefix(prefix, string):
242 """Return string, without the prefix. Blow up if string doesn't
243 start with prefix."""
244 assert string.startswith(prefix)
245 return string[len(prefix):]
247 def sanitize(s):
248 """Removes shell metacharacters from a string."""
249 for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
250 s = s.replace(c, '_')
251 return s
253 def is_linux():
254 """Is this a linux machine?"""
255 while True:
256 try:
257 return platform.system() == 'Linux'
258 except IOError, e:
259 if e.errno == errno.EINTR:
260 continue
261 raise e
263 def is_debian():
264 """Is it debian?"""
265 return os.path.exists('/usr/bin/apt-get')
268 def is_darwin():
269 """Return True on OSX."""
270 while True:
271 try:
272 p = platform.platform()
273 break
274 except IOError, e:
275 if e.errno == errno.EINTR:
276 continue
277 raise e
278 p = p.lower()
279 return 'macintosh' in p or 'darwin' in p
282 _is_win32 = None
283 def is_win32():
284 """Return True on win32"""
285 global _is_win32
286 if _is_win32 is None:
287 _is_win32 = os.name in ('nt', 'dos')
288 return _is_win32
291 def is_broken():
292 """Is it windows or mac? (e.g. is running git-mergetool non-trivial?)"""
293 if is_darwin():
294 return True
295 while True:
296 try:
297 return platform.system() == 'Windows'
298 except IOError, e:
299 if e.errno == errno.EINTR:
300 continue
301 raise e
304 def checksum(path):
305 """Return a cheap md5 hexdigest for a path."""
306 md5 = hashlib.new('md5')
307 md5.update(slurp(path))
308 return md5.hexdigest()
311 def quote_repopath(repopath):
312 """Quote a path for nt/dos only."""
313 if is_win32():
314 repopath = '"%s"' % repopath
315 return repopath