1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous utility functions."""
14 from cola
import resources
15 from cola
.compat
import hashlib
17 random
.seed(hash(time
.time()))
20 KNOWN_FILE_MIME_TYPES
= {
23 'python': 'script.png',
25 'shell': 'script.png',
27 'octet': 'binary.png',
30 KNOWN_FILE_EXTENSION
= {
31 '.java': 'script.png',
32 '.groovy': 'script.png',
40 def add_parents(path_entry_set
):
41 """Iterate over each item in the set and add its parent directories."""
42 for path
in list(path_entry_set
):
44 path
= path
.replace('//', '/')
45 if path
not in path_entry_set
:
46 path_entry_set
.add(path
)
48 parent_dir
= dirname(path
)
49 while parent_dir
and parent_dir
not in path_entry_set
:
50 path_entry_set
.add(parent_dir
)
51 parent_dir
= dirname(parent_dir
)
55 def ident_file_type(filename
):
56 """Returns an icon based on the contents of filename."""
57 if core
.exists(filename
):
58 filemimetype
= mimetypes
.guess_type(filename
)
59 if filemimetype
[0] != None:
60 for filetype
, iconname
in KNOWN_FILE_MIME_TYPES
.iteritems():
61 if filetype
in filemimetype
[0].lower():
63 filename
= filename
.lower()
64 for fileext
, iconname
in KNOWN_FILE_EXTENSION
.iteritems():
65 if filename
.endswith(fileext
):
70 # Fallback for modified files of an unknown type
74 def file_icon(filename
):
76 Returns the full path to an icon file corresponding to
79 return resources
.icon(ident_file_type(filename
))
82 def format_exception(e
):
83 exc_type
, exc_value
, exc_tb
= sys
.exc_info()
84 details
= traceback
.format_exception(exc_type
, exc_value
, exc_tb
)
85 details
= '\n'.join(details
)
94 """Subtracts list b from list a and returns the resulting list."""
95 # conceptually, c = a - b
104 def grep(pattern
, items
, squash
=True):
105 """Greps a list for items that match a pattern and return a list of
106 matching items. If only one item matches, return just that item.
108 isdict
= type(items
) is dict
109 if pattern
in __grep_cache
:
110 regex
= __grep_cache
[pattern
]
112 regex
= __grep_cache
[pattern
] = re
.compile(pattern
)
116 match
= regex
.match(item
)
119 groups
= match
.groups()
121 subitems
= match
.group(0)
126 subitems
= list(groups
)
128 matchdict
[item
] = items
[item
]
130 matched
.append(subitems
)
135 if squash
and len(matched
) == 1:
143 An os.path.basename() implementation that always uses '/'
145 Avoid os.path.basename because git's output always
146 uses '/' regardless of platform.
149 return path
.rsplit('/', 1)[-1]
153 """Strip one level of directory
155 >>> strip_one('/usr/bin/git')
158 >>> strip_one('local/bin/git')
161 >>> strip_one('bin/git')
168 return path
.strip('/').split('/', 1)[-1]
173 An os.path.dirname() implementation that always uses '/'
175 Avoid os.path.dirname because git's output always
176 uses '/' regardless of platform.
180 path
= path
.replace('//', '/')
181 path_dirname
= path
.rsplit('/', 1)[0]
182 if path_dirname
== path
:
184 return path
.rsplit('/', 1)[0]
187 def strip_prefix(prefix
, string
):
188 """Return string, without the prefix. Blow up if string doesn't
189 start with prefix."""
190 assert string
.startswith(prefix
)
191 return string
[len(prefix
):]
195 """Removes shell metacharacters from a string."""
196 for c
in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
197 s
= s
.replace(c
, '_')
201 def tablength(word
, tabwidth
):
202 """Return length of a word taking tabs into account
204 >>> tablength("\\t\\t\\t\\tX", 8)
208 return len(word
.replace('\t', '')) + word
.count('\t') * tabwidth
212 """Split string apart into utf-8 encoded words using shell syntax"""
214 return shlex
.split(core
.encode(s
))
216 return [core
.encode(s
)]
220 """Returns a unicode list instead of encoded strings"""
221 return [core
.decode(arg
) for arg
in _shell_split(s
)]
225 # Allow TMPDIR/TMP with a fallback to /tmp
226 return core
.getenv('TMP', core
.getenv('TMPDIR', '/tmp'))
229 def tmp_file_pattern():
230 return os
.path
.join(tmp_dir(), 'git-cola-%s-.*' % os
.getpid())
233 def tmp_filename(prefix
):
234 randstr
= ''.join([chr(random
.randint(ord('a'), ord('z')))
236 prefix
= prefix
.replace('/', '-').replace('\\', '-')
237 basename
= 'git-cola-%s-%s-%s' % (os
.getpid(), randstr
, prefix
)
238 return os
.path
.join(tmp_dir(), basename
)
242 """Is this a linux machine?"""
243 return sys
.platform
.startswith('linux')
248 return os
.path
.exists('/usr/bin/apt-get')
252 """Return True on OSX."""
253 return sys
.platform
== 'darwin'
257 """Return True on win32"""
258 return sys
.platform
== 'win32' or sys
.platform
== 'cygwin'
262 """Return a cheap md5 hexdigest for a path."""
263 md5
= hashlib
.new('md5')
264 md5
.update(core
.read(path
))
265 return md5
.hexdigest()
268 class ProgressIndicator(object):
270 """Simple progress indicator.
272 Displayed as a spinning character by default, but can be customized
273 by passing custom messages that overrides the spinning character.
277 States
= ("|", "/", "-", "\\")
279 def __init__(self
, prefix
="", f
=sys
.stdout
):
280 """Create a new ProgressIndicator, bound to the given file object."""
281 self
.n
= 0 # Simple progress counter
282 self
.f
= f
# Progress is written to this file object
283 self
.prev_len
= 0 # Length of previous msg (to be overwritten)
284 self
.prefix
= prefix
# Prefix prepended to each progress message
285 self
.prefix_lens
= [] # Stack of prefix string lengths
287 def pushprefix(self
, prefix
):
288 """Append the given prefix onto the prefix stack."""
289 self
.prefix_lens
.append(len(self
.prefix
))
290 self
.prefix
+= prefix
293 """Remove the last prefix from the prefix stack."""
294 prev_len
= self
.prefix_lens
.pop()
295 self
.prefix
= self
.prefix
[:prev_len
]
297 def __call__(self
, msg
= None, lf
=False):
298 """Indicate progress, possibly with a custom message."""
300 msg
= self
.States
[self
.n
% len(self
.States
)]
301 msg
= self
.prefix
+ msg
302 print >> self
.f
, "\r%-*s" % (self
.prev_len
, msg
),
303 self
.prev_len
= len(msg
.expandtabs())
309 def finish (self
, msg
="done", noprefix
=False):
310 """Finalize progress indication with the given message."""