1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous utility functions."""
17 from cola
import resources
18 from cola
.compat
import hashlib
19 from cola
.decorators
import memoize
21 random
.seed(hash(time
.time()))
24 KNOWN_FILE_MIME_TYPES
= {
27 'python': 'script.png',
29 'shell': 'script.png',
31 'octet': 'binary.png',
34 KNOWN_FILE_EXTENSION
= {
35 '.java': 'script.png',
36 '.groovy': 'script.png',
44 def add_parents(path_entry_set
):
45 """Iterate over each item in the set and add its parent directories."""
46 for path
in list(path_entry_set
):
48 path
= path
.replace('//', '/')
49 if path
not in path_entry_set
:
50 path_entry_set
.add(path
)
52 parent_dir
= dirname(path
)
53 while parent_dir
and parent_dir
not in path_entry_set
:
54 path_entry_set
.add(parent_dir
)
55 parent_dir
= dirname(parent_dir
)
61 Run arguments as a command and return output.
63 >>> run_cmd(["echo", "hello", "world"])
67 return git
.Git
.execute(command
)
70 def ident_file_type(filename
):
71 """Returns an icon based on the contents of filename."""
72 if os
.path
.exists(filename
):
73 filemimetype
= mimetypes
.guess_type(filename
)
74 if filemimetype
[0] != None:
75 for filetype
, iconname
in KNOWN_FILE_MIME_TYPES
.iteritems():
76 if filetype
in filemimetype
[0].lower():
78 filename
= filename
.lower()
79 for fileext
, iconname
in KNOWN_FILE_EXTENSION
.iteritems():
80 if filename
.endswith(fileext
):
85 # Fallback for modified files of an unknown type
89 def file_icon(filename
):
91 Returns the full path to an icon file corresponding to
94 return resources
.icon(ident_file_type(filename
))
98 """Launch a command in the background."""
99 encoded_args
= [core
.encode(arg
) for arg
in args
]
100 return subprocess
.Popen(encoded_args
).pid
104 """Subtracts list b from list a and returns the resulting list."""
105 # conceptually, c = a - b
114 def grep(pattern
, items
, squash
=True):
115 """Greps a list for items that match a pattern and return a list of
116 matching items. If only one item matches, return just that item.
118 isdict
= type(items
) is dict
119 if pattern
in __grep_cache
:
120 regex
= __grep_cache
[pattern
]
122 regex
= __grep_cache
[pattern
] = re
.compile(pattern
)
126 match
= regex
.match(item
)
129 groups
= match
.groups()
131 subitems
= match
.group(0)
136 subitems
= list(groups
)
138 matchdict
[item
] = items
[item
]
140 matched
.append(subitems
)
145 if squash
and len(matched
) == 1:
153 An os.path.basename() implementation that always uses '/'
155 Avoid os.path.basename because git's output always
156 uses '/' regardless of platform.
159 return path
.rsplit('/', 1)[-1]
164 An os.path.dirname() implementation that always uses '/'
166 Avoid os.path.dirname because git's output always
167 uses '/' regardless of platform.
171 path
= path
.replace('//', '/')
172 path_dirname
= path
.rsplit('/', 1)[0]
173 if path_dirname
== path
:
175 return path
.rsplit('/', 1)[0]
179 """Slurps a filepath into a string."""
180 fh
= open(core
.encode(path
))
181 slushy
= core
.read_nointr(fh
)
183 return core
.decode(slushy
)
186 def write(path
, contents
):
187 """Writes a raw string to a file."""
188 fh
= open(core
.encode(path
), 'wb')
189 core
.write_nointr(fh
, core
.encode(contents
))
193 def strip_prefix(prefix
, string
):
194 """Return string, without the prefix. Blow up if string doesn't
195 start with prefix."""
196 assert string
.startswith(prefix
)
197 return string
[len(prefix
):]
200 """Removes shell metacharacters from a string."""
201 for c
in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
202 s
= s
.replace(c
, '_')
207 """Split string apart into utf-8 encoded words using shell syntax"""
209 return shlex
.split(core
.encode(s
))
215 """Returns a unicode list instead of encoded strings"""
216 return [core
.decode(arg
) for arg
in shell_split(s
)]
220 # Allow TMPDIR/TMP with a fallback to /tmp
221 return os
.environ
.get('TMP', os
.environ
.get('TMPDIR', '/tmp'))
224 def tmp_file_pattern():
225 return os
.path
.join(tmp_dir(), 'git-cola-%s-.*' % os
.getpid())
228 def tmp_filename(prefix
):
229 randstr
= ''.join([chr(random
.randint(ord('a'), ord('z'))) for i
in range(7)])
230 prefix
= prefix
.replace('/', '-').replace('\\', '-')
231 basename
= 'git-cola-%s-%s-%s' % (os
.getpid(), randstr
, prefix
)
232 return os
.path
.join(tmp_dir(), basename
)
236 """Is this a linux machine?"""
239 return platform
.system() == 'Linux'
241 if e
.errno
== errno
.EINTR
:
247 return os
.path
.exists('/usr/bin/apt-get')
251 """Return True on OSX."""
254 p
= platform
.platform()
257 if e
.errno
== errno
.EINTR
:
261 return 'macintosh' in p
or 'darwin' in p
266 """Return True on win32"""
267 return os
.name
in ('nt', 'dos')
270 def win32_set_binary(fd
):
275 # When run without console, pipes may expose invalid
276 # fileno(), usually set to -1.
277 if hasattr(fd
, 'fileno') and fd
.fileno() >= 0:
278 msvcrt
.setmode(fd
.fileno(), os
.O_BINARY
)
281 def posix_set_binary(fd
):
282 """POSIX file descriptors are always binary"""
287 set_binary
= win32_set_binary
289 set_binary
= posix_set_binary
293 """Return a cheap md5 hexdigest for a path."""
294 md5
= hashlib
.new('md5')
295 md5
.update(slurp(path
))
296 return md5
.hexdigest()
299 def error(msg
, *args
):
300 """Print an error message to stderr."""
301 print >> sys
.stderr
, "ERROR:", msg
% args
304 def warn(msg
, *args
):
305 """Print a warning message to stderr."""
306 print >> sys
.stderr
, "warning:", msg
% args
310 """Print as error message to stderr and exit the program."""
315 class ProgressIndicator(object):
317 """Simple progress indicator.
319 Displayed as a spinning character by default, but can be customized
320 by passing custom messages that overrides the spinning character.
324 States
= ("|", "/", "-", "\\")
326 def __init__(self
, prefix
="", f
=sys
.stdout
):
327 """Create a new ProgressIndicator, bound to the given file object."""
328 self
.n
= 0 # Simple progress counter
329 self
.f
= f
# Progress is written to this file object
330 self
.prev_len
= 0 # Length of previous msg (to be overwritten)
331 self
.prefix
= prefix
# Prefix prepended to each progress message
332 self
.prefix_lens
= [] # Stack of prefix string lengths
334 def pushprefix(self
, prefix
):
335 """Append the given prefix onto the prefix stack."""
336 self
.prefix_lens
.append(len(self
.prefix
))
337 self
.prefix
+= prefix
340 """Remove the last prefix from the prefix stack."""
341 prev_len
= self
.prefix_lens
.pop()
342 self
.prefix
= self
.prefix
[:prev_len
]
344 def __call__(self
, msg
= None, lf
=False):
345 """Indicate progress, possibly with a custom message."""
347 msg
= self
.States
[self
.n
% len(self
.States
)]
348 msg
= self
.prefix
+ msg
349 print >> self
.f
, "\r%-*s" % (self
.prev_len
, msg
),
350 self
.prev_len
= len(msg
.expandtabs())
356 def finish (self
, msg
="done", noprefix
=False):
357 """Finalize progress indication with the given message."""
363 def start_command(args
, cwd
=None, shell
=False, add_env
=None,
364 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
,
365 stderr
=subprocess
.PIPE
):
366 """Start the given command, and return a subprocess object.
368 This provides a simpler interface to the subprocess module.
372 if add_env
is not None:
373 env
= os
.environ
.copy()
375 return subprocess
.Popen(args
, bufsize
=1, stdin
=stdin
, stdout
=stdout
,
376 stderr
=stderr
, cwd
=cwd
, shell
=shell
,
377 env
=env
, universal_newlines
=True)
380 def run_command(args
, cwd
=None, shell
=False, add_env
=None,
382 """Run the given command to completion, and return its results.
384 This provides a simpler interface to the subprocess module.
386 The results are formatted as a 3-tuple: (exit_code, output, errors)
388 If flag_error is enabled, Error messages will be produced if the
389 subprocess terminated with a non-zero exit code and/or stderr
392 The other arguments are passed on to start_command().
395 process
= start_command(args
, cwd
, shell
, add_env
)
396 (output
, errors
) = process
.communicate()
397 exit_code
= process
.returncode
398 if flag_error
and errors
:
399 error("'%s' returned errors:\n---\n%s---", " ".join(args
), errors
)
400 if flag_error
and exit_code
:
401 error("'%s' returned exit code %i", " ".join(args
), exit_code
)
402 return (exit_code
, output
, errors
)