1 # Copyright (C) 2007-2018 David Aguilar and contributors
2 """This module provides miscellaneous utility functions."""
3 from __future__
import division
, absolute_import
, unicode_literals
17 random
.seed(hash(time
.time()))
20 def asint(obj
, default
=0):
21 """Make any value into an int, even if the cast fails"""
24 except (TypeError, ValueError):
29 def clamp(value
, lo
, hi
):
30 """Clamp a value to the specified range"""
31 return min(hi
, max(lo
, value
))
35 return int(time
.time() * 1000)
38 def add_parents(paths
):
39 """Iterate over each item in the set and add its parent directories."""
43 path
= path
.replace('//', '/')
46 parent_dir
= dirname(path
)
48 all_paths
.add(parent_dir
)
49 parent_dir
= dirname(parent_dir
)
53 def format_exception(e
):
54 exc_type
, exc_value
, exc_tb
= sys
.exc_info()
55 details
= traceback
.format_exception(exc_type
, exc_value
, exc_tb
)
56 details
= '\n'.join(map(core
.decode
, details
))
60 msg
= core
.decode(repr(e
))
65 """Subtracts list b from list a and returns the resulting list."""
66 # conceptually, c = a - b
77 def grep(pattern
, items
, squash
=True):
78 """Greps a list for items that match a pattern
80 :param squash: If only one item matches, return just that item
81 :returns: List of matching items
84 isdict
= isinstance(items
, dict)
85 if pattern
in __grep_cache
:
86 regex
= __grep_cache
[pattern
]
88 regex
= __grep_cache
[pattern
] = re
.compile(pattern
)
92 match
= regex
.match(item
)
95 groups
= match
.groups()
97 subitems
= match
.group(0)
102 subitems
= list(groups
)
104 matchdict
[item
] = items
[item
]
106 matched
.append(subitems
)
110 elif squash
and len(matched
) == 1:
120 An os.path.basename() implementation that always uses '/'
122 Avoid os.path.basename because git's output always
123 uses '/' regardless of platform.
126 return path
.rsplit('/', 1)[-1]
130 """Strip one level of directory"""
131 return path
.strip('/').split('/', 1)[-1]
134 def dirname(path
, current_dir
=''):
136 An os.path.dirname() implementation that always uses '/'
138 Avoid os.path.dirname because git's output always
139 uses '/' regardless of platform.
143 path
= path
.replace('//', '/')
144 path_dirname
= path
.rsplit('/', 1)[0]
145 if path_dirname
== path
:
147 return path
.rsplit('/', 1)[0]
151 """Split paths using '/' regardless of platform
154 return path
.split('/')
158 """Join paths using '/' regardless of platform
161 return '/'.join(paths
)
165 """Return all of the path components for the specified path
167 >>> pathset('foo/bar/baz') == ['foo', 'foo/bar', 'foo/bar/baz']
172 parts
= splitpath(path
)
175 result
.append(prefix
+ part
)
181 def select_directory(paths
):
182 """Return the first directory in a list of paths"""
190 return os
.path
.dirname(paths
[0])
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
):]
201 """Removes shell metacharacters from a string."""
202 for c
in """ \t!@#$%^&*()\\;,<>"'[]{}~|""":
203 s
= s
.replace(c
, '_')
207 def tablength(word
, tabwidth
):
208 """Return length of a word taking tabs into account
210 >>> tablength("\\t\\t\\t\\tX", 8)
214 return len(word
.replace('\t', '')) + word
.count('\t') * tabwidth
217 def _shell_split_py2(s
):
218 """Python2 requires bytes inputs to shlex.split(). Returns [unicode]"""
220 result
= shlex
.split(core
.encode(s
))
222 result
= core
.encode(s
).strip().split()
223 # Decode to unicode strings
224 return [core
.decode(arg
) for arg
in result
]
227 def _shell_split_py3(s
):
228 """Python3 requires unicode inputs to shlex.split(). Converts to unicode"""
230 result
= shlex
.split(s
)
232 result
= core
.decode(s
).strip().split()
239 # Encode before calling split()
240 values
= _shell_split_py2(s
)
242 # Python3 does not need the encode/decode dance
243 values
= _shell_split_py3(s
)
247 def tmp_filename(label
, suffix
=''):
248 label
= 'git-cola-' + label
.replace('/', '-').replace('\\', '-')
249 fd
= tempfile
.NamedTemporaryFile(prefix
=label
+'-', suffix
=suffix
)
255 """Is this a linux machine?"""
256 return sys
.platform
.startswith('linux')
261 return os
.path
.exists('/usr/bin/apt-get')
265 """Return True on OSX."""
266 return sys
.platform
== 'darwin'
270 """Return True on win32"""
271 return sys
.platform
== 'win32' or sys
.platform
== 'cygwin'
274 def expandpath(path
):
275 """Expand ~user/ and environment $variables"""
276 path
= os
.path
.expandvars(path
)
277 if path
.startswith('~'):
278 path
= os
.path
.expanduser(path
)
283 """Operate on a collection of objects as a single unit"""
285 def __init__(self
, *members
):
286 self
._members
= members
288 def __getattr__(self
, name
):
289 """Return a function that relays calls to the group"""
290 def relay(*args
, **kwargs
):
291 for member
in self
._members
:
292 method
= getattr(member
, name
)
293 method(*args
, **kwargs
)
294 setattr(self
, name
, relay
)
299 """Wrap an object and override attributes"""
301 def __init__(self
, obj
, **overrides
):
303 for k
, v
in overrides
.items():
306 def __getattr__(self
, name
):
307 return getattr(self
._obj
, name
)
310 def slice_fn(input_items
, map_fn
):
311 """Slice input_items and call map_fn over every slice
313 This exists because of "errno: Argument list too long"
316 # This comment appeared near the top of include/linux/binfmts.h
317 # in the Linux source tree:
320 # * MAX_ARG_PAGES defines the number of pages allocated for arguments
321 # * and envelope for the new program. 32 should suffice, this gives
322 # * a maximum env+arg of 128kB w/4KB pages!
324 # #define MAX_ARG_PAGES 32
326 # 'size' is a heuristic to keep things highly performant by minimizing
327 # the number of slices. If we wanted it to run as few commands as
328 # possible we could call "getconf ARG_MAX" and make a better guess,
329 # but it's probably not worth the complexity (and the extra call to
330 # getconf that we can't do on Windows anyways).
332 # In my testing, getconf ARG_MAX on Mac OS X Mountain Lion reported
333 # 262144 and Debian/Linux-x86_64 reported 2097152.
335 # The hard-coded max_arg_len value is safely below both of these
338 # 4K pages x 32 MAX_ARG_PAGES
339 max_arg_len
= (32 * 4096) // 4 # allow plenty of space for the environment
340 max_filename_len
= 256
341 size
= max_arg_len
// max_filename_len
347 items
= copy
.copy(input_items
)
349 stat
, out
, err
= map_fn(items
[:size
])
351 status
= min(stat
, status
)
353 status
= max(stat
, status
)
358 return (status
, '\n'.join(outs
), '\n'.join(errs
))
363 def __init__(self
, sequence
):
366 def index(self
, item
, default
=-1):
368 idx
= self
.seq
.index(item
)
373 def __getitem__(self
, idx
):