doc: Do not mention unused cola.geometry variable
[git-cola.git] / cola / utils.py
blob474c5bd6ca0459e48725dab978942a24953651f7
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 qm_for_locale(locale):
68 """Returns the .qm file for a particular $LANG values."""
69 regex = re.compile(r'([^\.])+\..*$')
70 match = regex.match(locale)
71 if match:
72 locale = match.group(1)
73 return resources.qm(locale.split('_')[0])
76 def ident_file_type(filename):
77 """Returns an icon based on the contents of filename."""
78 if os.path.exists(filename):
79 filemimetype = mimetypes.guess_type(filename)
80 if filemimetype[0] != None:
81 for filetype, iconname in KNOWN_FILE_MIME_TYPES.iteritems():
82 if filetype in filemimetype[0].lower():
83 return iconname
84 filename = filename.lower()
85 for fileext, iconname in KNOWN_FILE_EXTENSION.iteritems():
86 if filename.endswith(fileext):
87 return iconname
88 return 'generic.png'
89 else:
90 return 'removed.png'
91 # Fallback for modified files of an unknown type
92 return 'generic.png'
95 def file_icon(filename):
96 """
97 Returns the full path to an icon file corresponding to
98 filename"s contents.
99 """
100 return resources.icon(ident_file_type(filename))
103 def win32_abspath(exe):
104 """Return the absolute path to an .exe if it exists"""
105 if os.path.exists(exe):
106 return exe
107 if not exe.endswith('.exe'):
108 exe += '.exe'
109 if os.path.exists(exe):
110 return exe
111 for path in os.environ['PATH'].split(os.pathsep):
112 abspath = os.path.join(path, exe)
113 if os.path.exists(abspath):
114 return abspath
115 return None
118 def win32_expand_paths(args):
119 """Expand filenames after the double-dash"""
120 if '--' not in args:
121 return args
122 dashes_idx = args.index('--')
123 cmd = args[:dashes_idx+1]
124 for path in args[dashes_idx+1:]:
125 cmd.append(shell_quote(os.path.join(os.getcwd(), path)))
126 return cmd
129 def fork(args):
130 """Launch a command in the background."""
131 if os.name in ('nt', 'dos'):
132 # Windows is absolutely insane.
134 # If we want to launch 'gitk' we have to use the 'sh -c' trick.
136 # If we want to launch 'git.exe' we have to expand all filenames
137 # after the double-dash.
139 # os.spawnv wants an absolute path in the command name but not in
140 # the command vector. Wow.
141 enc_args = win32_expand_paths([core.encode(a) for a in args])
142 abspath = win32_abspath(enc_args[0])
143 if abspath:
144 # e.g. fork(['git', 'difftool', '--no-prompt', '--', 'path'])
145 return os.spawnv(os.P_NOWAIT, abspath, enc_args)
147 # e.g. fork(['gitk', '--all'])
148 sh_exe = win32_abspath('sh')
149 enc_argv = map(shell_quote, enc_args)
150 cmdstr = ' '.join(enc_argv)
151 cmd = ['sh.exe', '-c', cmdstr]
152 return os.spawnv(os.P_NOWAIT, sh_exe, cmd)
153 else:
154 # Unix is absolutely simple
155 enc_args = [core.encode(a) for a in args]
156 enc_argv = map(shell_quote, enc_args)
157 cmdstr = ' '.join(enc_argv)
158 return os.system(cmdstr + '&')
161 def sublist(a,b):
162 """Subtracts list b from list a and returns the resulting list."""
163 # conceptually, c = a - b
164 c = []
165 for item in a:
166 if item not in b:
167 c.append(item)
168 return c
171 __grep_cache = {}
172 def grep(pattern, items, squash=True):
173 """Greps a list for items that match a pattern and return a list of
174 matching items. If only one item matches, return just that item.
176 isdict = type(items) is dict
177 if pattern in __grep_cache:
178 regex = __grep_cache[pattern]
179 else:
180 regex = __grep_cache[pattern] = re.compile(pattern)
181 matched = []
182 matchdict = {}
183 for item in items:
184 match = regex.match(item)
185 if not match:
186 continue
187 groups = match.groups()
188 if not groups:
189 subitems = match.group(0)
190 else:
191 if len(groups) == 1:
192 subitems = groups[0]
193 else:
194 subitems = list(groups)
195 if isdict:
196 matchdict[item] = items[item]
197 else:
198 matched.append(subitems)
200 if isdict:
201 return matchdict
202 else:
203 if squash and len(matched) == 1:
204 return matched[0]
205 else:
206 return matched
209 def basename(path):
211 An os.path.basename() implementation that always uses '/'
213 Avoid os.path.basename because git's output always
214 uses '/' regardless of platform.
217 return path.rsplit('/', 1)[-1]
220 def dirname(path):
222 An os.path.dirname() implementation that always uses '/'
224 Avoid os.path.dirname because git's output always
225 uses '/' regardless of platform.
228 while '//' in path:
229 path = path.replace('//', '/')
230 path_dirname = path.rsplit('/', 1)[0]
231 if path_dirname == path:
232 return ''
233 return path.rsplit('/', 1)[0]
236 def slurp(path):
237 """Slurps a filepath into a string."""
238 fh = open(path)
239 slushy = core.read_nointr(fh)
240 fh.close()
241 return core.decode(slushy)
244 def write(path, contents):
245 """Writes a string to a file."""
246 fh = open(path, 'w')
247 core.write_nointr(fh, core.encode(contents))
248 fh.close()
250 def strip_prefix(prefix, string):
251 """Return string, without the prefix. Blow up if string doesn't
252 start with prefix."""
253 assert string.startswith(prefix)
254 return string[len(prefix):]
256 def sanitize(s):
257 """Removes shell metacharacters from a string."""
258 for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
259 s = s.replace(c, '_')
260 return s
262 def is_linux():
263 """Is this a linux machine?"""
264 while True:
265 try:
266 return platform.system() == 'Linux'
267 except IOError, e:
268 if e.errno == errno.EINTR:
269 continue
270 raise e
272 def is_debian():
273 """Is it debian?"""
274 return os.path.exists('/usr/bin/apt-get')
277 def is_darwin():
278 """Return True on OSX."""
279 while True:
280 try:
281 p = platform.platform()
282 break
283 except IOError, e:
284 if e.errno == errno.EINTR:
285 continue
286 raise e
287 p = p.lower()
288 return 'macintosh' in p or 'darwin' in p
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 os.name in ('nt', 'dos'):
314 repopath = '"%s"' % repopath
315 return repopath