git-cola v2.11
[git-cola.git] / cola / utils.py
blob3e868df05e3e22562e30ea4ce12db8acceb22974
1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous utility functions."""
3 from __future__ import division, absolute_import, unicode_literals
4 import os
5 import random
6 import re
7 import shlex
8 import sys
9 import tempfile
10 import time
11 import traceback
13 from . import core
15 random.seed(hash(time.time()))
18 def add_parents(paths):
19 """Iterate over each item in the set and add its parent directories."""
20 all_paths = set()
21 for path in paths:
22 while '//' in path:
23 path = path.replace('//', '/')
24 all_paths.add(path)
25 if '/' in path:
26 parent_dir = dirname(path)
27 while parent_dir:
28 all_paths.add(parent_dir)
29 parent_dir = dirname(parent_dir)
30 return all_paths
33 def format_exception(e):
34 exc_type, exc_value, exc_tb = sys.exc_info()
35 details = traceback.format_exception(exc_type, exc_value, exc_tb)
36 details = '\n'.join(map(core.decode, details))
37 if hasattr(e, 'msg'):
38 msg = e.msg
39 else:
40 msg = core.decode(repr(e))
41 return (msg, details)
44 def sublist(a, b):
45 """Subtracts list b from list a and returns the resulting list."""
46 # conceptually, c = a - b
47 c = []
48 for item in a:
49 if item not in b:
50 c.append(item)
51 return c
54 __grep_cache = {}
57 def grep(pattern, items, squash=True):
58 """Greps a list for items that match a pattern
60 :param squash: If only one item matches, return just that item
61 :returns: List of matching items
63 """
64 isdict = type(items) is dict
65 if pattern in __grep_cache:
66 regex = __grep_cache[pattern]
67 else:
68 regex = __grep_cache[pattern] = re.compile(pattern)
69 matched = []
70 matchdict = {}
71 for item in items:
72 match = regex.match(item)
73 if not match:
74 continue
75 groups = match.groups()
76 if not groups:
77 subitems = match.group(0)
78 else:
79 if len(groups) == 1:
80 subitems = groups[0]
81 else:
82 subitems = list(groups)
83 if isdict:
84 matchdict[item] = items[item]
85 else:
86 matched.append(subitems)
88 if isdict:
89 return matchdict
90 else:
91 if squash and len(matched) == 1:
92 return matched[0]
93 else:
94 return matched
97 def basename(path):
98 """
99 An os.path.basename() implementation that always uses '/'
101 Avoid os.path.basename because git's output always
102 uses '/' regardless of platform.
105 return path.rsplit('/', 1)[-1]
108 def strip_one(path):
109 """Strip one level of directory"""
110 return path.strip('/').split('/', 1)[-1]
113 def dirname(path, current_dir=''):
115 An os.path.dirname() implementation that always uses '/'
117 Avoid os.path.dirname because git's output always
118 uses '/' regardless of platform.
121 while '//' in path:
122 path = path.replace('//', '/')
123 path_dirname = path.rsplit('/', 1)[0]
124 if path_dirname == path:
125 return current_dir
126 return path.rsplit('/', 1)[0]
129 def select_directory(paths):
130 """Return the first directory in a list of paths"""
131 if not paths:
132 return core.getcwd()
134 for path in paths:
135 if core.isdir(path):
136 return path
138 return os.path.dirname(paths[0])
141 def strip_prefix(prefix, string):
142 """Return string, without the prefix. Blow up if string doesn't
143 start with prefix."""
144 assert string.startswith(prefix)
145 return string[len(prefix):]
148 def sanitize(s):
149 """Removes shell metacharacters from a string."""
150 for c in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
151 s = s.replace(c, '_')
152 return s
155 def tablength(word, tabwidth):
156 """Return length of a word taking tabs into account
158 >>> tablength("\\t\\t\\t\\tX", 8)
162 return len(word.replace('\t', '')) + word.count('\t') * tabwidth
165 def _shell_split(s):
166 """Split string apart into utf-8 encoded words using shell syntax"""
167 try:
168 return shlex.split(core.encode(s))
169 except ValueError:
170 return [core.encode(s)]
173 if sys.version_info[0] == 3:
174 # In Python 3, we don't need the encode/decode dance
175 shell_split = shlex.split
176 else:
177 def shell_split(s):
178 """Returns a unicode list instead of encoded strings"""
179 return [core.decode(arg) for arg in _shell_split(s)]
182 def tmp_filename(label):
183 label = 'git-cola-' + label.replace('/', '-').replace('\\', '-')
184 fd = tempfile.NamedTemporaryFile(prefix=label+'-')
185 fd.close()
186 return fd.name
189 def is_linux():
190 """Is this a linux machine?"""
191 return sys.platform.startswith('linux')
194 def is_debian():
195 """Is it debian?"""
196 return os.path.exists('/usr/bin/apt-get')
199 def is_darwin():
200 """Return True on OSX."""
201 return sys.platform == 'darwin'
204 def is_win32():
205 """Return True on win32"""
206 return sys.platform == 'win32' or sys.platform == 'cygwin'
209 def expandpath(path):
210 """Expand ~user/ and environment $variables"""
211 path = os.path.expandvars(path)
212 if path.startswith('~'):
213 path = os.path.expanduser(path)
214 return path
217 class Group(object):
218 """Operate on a collection of objects as a single unit"""
220 def __init__(self, *members):
221 self._members = members
223 def __getattr__(self, name):
224 """Return a function that relays calls to the group"""
225 def relay(*args, **kwargs):
226 for member in self._members:
227 method = getattr(member, name)
228 method(*args, **kwargs)
229 setattr(self, name, relay)
230 return relay
233 class Proxy(object):
234 """Wrap an object and override attributes"""
236 def __init__(self, obj, **overrides):
237 self._obj = obj
238 for k, v in overrides.items():
239 setattr(self, k, v)
241 def __getattr__(self, name):
242 return getattr(self._obj, name)