qtutils: try to support older PyQt versions
[git-cola.git] / cola / utils.py
blob8b89bb020940a2c6e821a710e79a70ebf87e9954
1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous utility functions."""
3 from __future__ import division, absolute_import, unicode_literals
5 import os
6 import random
7 import re
8 import shlex
9 import sys
10 import tempfile
11 import time
12 import traceback
14 from cola import core
16 random.seed(hash(time.time()))
19 def add_parents(paths):
20 """Iterate over each item in the set and add its parent directories."""
21 path_entry_set = set(paths)
22 for path in list(path_entry_set):
23 while '//' in path:
24 path = path.replace('//', '/')
25 if path not in path_entry_set:
26 path_entry_set.add(path)
27 if '/' in path:
28 parent_dir = dirname(path)
29 while parent_dir and parent_dir not in path_entry_set:
30 path_entry_set.add(parent_dir)
31 parent_dir = dirname(parent_dir)
32 return path_entry_set
35 def format_exception(e):
36 exc_type, exc_value, exc_tb = sys.exc_info()
37 details = traceback.format_exception(exc_type, exc_value, exc_tb)
38 details = '\n'.join(details)
39 if hasattr(e, 'msg'):
40 msg = e.msg
41 else:
42 msg = str(e)
43 return (msg, details)
46 def sublist(a,b):
47 """Subtracts list b from list a and returns the resulting list."""
48 # conceptually, c = a - b
49 c = []
50 for item in a:
51 if item not in b:
52 c.append(item)
53 return c
56 __grep_cache = {}
57 def grep(pattern, items, squash=True):
58 """Greps a list for items that match a pattern and return a list of
59 matching items. If only one item matches, return just that item.
60 """
61 isdict = type(items) is dict
62 if pattern in __grep_cache:
63 regex = __grep_cache[pattern]
64 else:
65 regex = __grep_cache[pattern] = re.compile(pattern)
66 matched = []
67 matchdict = {}
68 for item in items:
69 match = regex.match(item)
70 if not match:
71 continue
72 groups = match.groups()
73 if not groups:
74 subitems = match.group(0)
75 else:
76 if len(groups) == 1:
77 subitems = groups[0]
78 else:
79 subitems = list(groups)
80 if isdict:
81 matchdict[item] = items[item]
82 else:
83 matched.append(subitems)
85 if isdict:
86 return matchdict
87 else:
88 if squash and len(matched) == 1:
89 return matched[0]
90 else:
91 return matched
94 def basename(path):
95 """
96 An os.path.basename() implementation that always uses '/'
98 Avoid os.path.basename because git's output always
99 uses '/' regardless of platform.
102 return path.rsplit('/', 1)[-1]
105 def strip_one(path):
106 """Strip one level of directory
108 >>> strip_one('/usr/bin/git')
109 u'bin/git'
111 >>> strip_one('local/bin/git')
112 u'bin/git'
114 >>> strip_one('bin/git')
115 u'git'
117 >>> strip_one('git')
118 u'git'
121 return path.strip('/').split('/', 1)[-1]
124 def dirname(path):
126 An os.path.dirname() implementation that always uses '/'
128 Avoid os.path.dirname because git's output always
129 uses '/' regardless of platform.
132 while '//' in path:
133 path = path.replace('//', '/')
134 path_dirname = path.rsplit('/', 1)[0]
135 if path_dirname == path:
136 return ''
137 return path.rsplit('/', 1)[0]
140 def strip_prefix(prefix, string):
141 """Return string, without the prefix. Blow up if string doesn't
142 start with prefix."""
143 assert string.startswith(prefix)
144 return string[len(prefix):]
147 def sanitize(s):
148 """Removes shell metacharacters from a string."""
149 for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
150 s = s.replace(c, '_')
151 return s
154 def tablength(word, tabwidth):
155 """Return length of a word taking tabs into account
157 >>> tablength("\\t\\t\\t\\tX", 8)
161 return len(word.replace('\t', '')) + word.count('\t') * tabwidth
164 def _shell_split(s):
165 """Split string apart into utf-8 encoded words using shell syntax"""
166 try:
167 return shlex.split(core.encode(s))
168 except ValueError:
169 return [core.encode(s)]
172 if sys.version_info[0] == 3:
173 # In Python 3, we don't need the encode/decode dance
174 shell_split = shlex.split
175 else:
176 def shell_split(s):
177 """Returns a unicode list instead of encoded strings"""
178 return [core.decode(arg) for arg in _shell_split(s)]
181 def tmp_file_pattern():
182 return os.path.join(tempfile.gettempdir(), 'git-cola-%s-*' % os.getpid())
185 def tmp_filename(label):
186 prefix = 'git-cola-%s-' % (os.getpid())
187 suffix = '-%s' % label.replace('/', '-').replace('\\', '-')
188 fd, path = tempfile.mkstemp(suffix, prefix)
189 os.close(fd)
190 return path
193 def is_linux():
194 """Is this a linux machine?"""
195 return sys.platform.startswith('linux')
198 def is_debian():
199 """Is it debian?"""
200 return os.path.exists('/usr/bin/apt-get')
203 def is_darwin():
204 """Return True on OSX."""
205 return sys.platform == 'darwin'
208 def is_win32():
209 """Return True on win32"""
210 return sys.platform == 'win32' or sys.platform == 'cygwin'
213 def expandpath(path):
214 """Expand ~user/ and environment $variables"""
215 path = os.path.expandvars(path)
216 if path.startswith('~'):
217 path = os.path.expanduser(path)
218 return path
221 class Group(object):
222 """Operate on a collection of objects as a single unit"""
224 def __init__(self, *members):
225 self._members = members
227 def __getattr__(self, name):
228 """Return a function that relays calls to the group"""
229 def relay(*args, **kwargs):
230 for member in self._members:
231 method = getattr(member, name)
232 method(*args, **kwargs)
233 setattr(self, name, relay)
234 return relay
237 class Proxy(object):
238 """Wrap an object and override attributes"""
240 def __init__(self, obj, **overrides):
241 self._obj = obj
242 for k, v in overrides.items():
243 setattr(self, k, v)
245 def __getattr__(self, name):
246 return getattr(self._obj, name)