1### 2# Copyright (c) 2002-2005, Jeremiah Fincher 3# Copyright (c) 2008, James McCoy 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are met: 8# 9# * Redistributions of source code must retain the above copyright notice, 10# this list of conditions, and the following disclaimer. 11# * Redistributions in binary form must reproduce the above copyright notice, 12# this list of conditions, and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# * Neither the name of the author of this software nor the name of 15# contributors to this software may be used to endorse or promote products 16# derived from this software without specific prior written consent. 17# 18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28# POSSIBILITY OF SUCH DAMAGE. 29### 30 31from __future__ import print_function 32 33import os 34import sys 35import ast 36import textwrap 37import warnings 38import functools 39import traceback 40import collections 41 42 43from . import crypt 44from .str import format 45from .file import mktemp 46from . import minisix 47from . import internationalization as _ 48 49def warn_non_constant_time(f): 50 @functools.wraps(f) 51 def newf(*args, **kwargs): 52 # This method takes linear time whereas the subclass could probably 53 # do it in constant time. 54 warnings.warn('subclass of IterableMap does provide an efficient ' 55 'implementation of %s' % f.__name__, 56 DeprecationWarning) 57 return f(*args, **kwargs) 58 return newf 59 60 61def abbrev(strings, d=None): 62 """Returns a dictionary mapping unambiguous abbreviations to full forms.""" 63 def eachSubstring(s): 64 for i in range(1, len(s)+1): 65 yield s[:i] 66 if len(strings) != len(set(strings)): 67 raise ValueError( 68 'strings given to utils.abbrev have duplicates: %r' % strings) 69 if d is None: 70 d = {} 71 for s in strings: 72 for abbreviation in eachSubstring(s): 73 if abbreviation not in d: 74 d[abbreviation] = s 75 else: 76 if abbreviation not in strings: 77 d[abbreviation] = None 78 removals = [] 79 for key in d: 80 if d[key] is None: 81 removals.append(key) 82 for key in removals: 83 del d[key] 84 return d 85 86def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True, 87 weeks=True, days=True, hours=True, minutes=True, seconds=True): 88 """Given <elapsed> seconds, returns a string with an English description of 89 the amount of time passed. leadingZeroes determines whether 0 days, 0 90 hours, etc. will be printed; the others determine what larger time periods 91 should be used. 92 """ 93 ret = [] 94 before = False 95 def Format(s, i): 96 if i or leadingZeroes or ret: 97 if short: 98 ret.append('%s%s' % (i, s[0])) 99 else: 100 ret.append(format('%n', (i, s))) 101 elapsed = int(elapsed) 102 103 # Handle negative times 104 if elapsed < 0: 105 before = True 106 elapsed = -elapsed 107 108 assert years or weeks or days or \ 109 hours or minutes or seconds, 'One flag must be True' 110 if years: 111 (yrs, elapsed) = (elapsed // 31536000, elapsed % 31536000) 112 Format(_('year'), yrs) 113 if weeks: 114 (wks, elapsed) = (elapsed // 604800, elapsed % 604800) 115 Format(_('week'), wks) 116 if days: 117 (ds, elapsed) = (elapsed // 86400, elapsed % 86400) 118 Format(_('day'), ds) 119 if hours: 120 (hrs, elapsed) = (elapsed // 3600, elapsed % 3600) 121 Format(_('hour'), hrs) 122 if minutes or seconds: 123 (mins, secs) = (elapsed // 60, elapsed % 60) 124 if leadingZeroes or mins: 125 Format(_('minute'), mins) 126 if seconds: 127 leadingZeroes = True 128 Format(_('second'), secs) 129 if not ret: 130 raise ValueError('Time difference not great enough to be noted.') 131 result = '' 132 if short: 133 result = ' '.join(ret) 134 else: 135 result = format('%L', ret) 136 if before: 137 result = _('%s ago') % result 138 return result 139 140def findBinaryInPath(s): 141 """Return full path of a binary if it's in PATH, otherwise return None.""" 142 cmdLine = None 143 for dir in os.getenv('PATH').split(':'): 144 filename = os.path.join(dir, s) 145 if os.path.exists(filename): 146 cmdLine = filename 147 break 148 return cmdLine 149 150def sortBy(f, L): 151 """Uses the decorate-sort-undecorate pattern to sort L by function f.""" 152 for (i, elt) in enumerate(L): 153 L[i] = (f(elt), i, elt) 154 L.sort() 155 for (i, elt) in enumerate(L): 156 L[i] = L[i][2] 157 158def saltHash(password, salt=None, hash='sha'): 159 if salt is None: 160 salt = mktemp()[:8] 161 if hash == 'sha': 162 hasher = crypt.sha 163 elif hash == 'md5': 164 hasher = crypt.md5 165 return '|'.join([salt, hasher((salt + password).encode('utf8')).hexdigest()]) 166 167_astStr2 = ast.Str if minisix.PY2 else ast.Bytes 168def safeEval(s, namespace=None): 169 """Evaluates s, safely. Useful for turning strings into tuples/lists/etc. 170 without unsafely using eval().""" 171 try: 172 node = ast.parse(s, mode='eval').body 173 except SyntaxError as e: 174 raise ValueError('Invalid string: %s.' % e) 175 def checkNode(node): 176 if node.__class__ is ast.Expr: 177 node = node.value 178 if node.__class__ in (ast.Num, 179 ast.Str, 180 _astStr2): 181 return True 182 elif node.__class__ in (ast.List, 183 ast.Tuple): 184 return all([checkNode(x) for x in node.elts]) 185 elif node.__class__ is ast.Dict: 186 return all([checkNode(x) for x in node.values]) and \ 187 all([checkNode(x) for x in node.values]) 188 elif node.__class__ is ast.Name: 189 if namespace is None and node.id in ('True', 'False', 'None'): 190 # For Python < 3.4, which does not have NameConstant. 191 return True 192 elif namespace is not None and node.id in namespace: 193 return True 194 else: 195 return False 196 elif sys.version_info[0:2] >= (3, 4) and \ 197 node.__class__ is ast.NameConstant: 198 return True 199 elif sys.version_info[0:2] >= (3, 8) and \ 200 node.__class__ is ast.Constant: 201 return True 202 else: 203 return False 204 if checkNode(node): 205 if namespace is None: 206 return eval(s, namespace, namespace) 207 else: 208 # Probably equivalent to eval() because checkNode(node) is True, 209 # but it's an extra security. 210 return ast.literal_eval(node) 211 else: 212 raise ValueError(format('Unsafe string: %q', s)) 213 214def exnToString(e): 215 """Turns a simple exception instance into a string (better than str(e))""" 216 strE = str(e) 217 if strE: 218 return '%s: %s' % (e.__class__.__name__, strE) 219 else: 220 return e.__class__.__name__ 221 222class IterableMap(object): 223 """Define .items() in a class and subclass this to get the other iters. 224 """ 225 def items(self): 226 if minisix.PY3 and hasattr(self, 'iteritems'): 227 # For old plugins 228 return self.iteritems() # avoid 2to3 229 else: 230 raise NotImplementedError() 231 __iter__ = items 232 233 def keys(self): 234 for (key, __) in self.items(): 235 yield key 236 237 def values(self): 238 for (__, value) in self.items(): 239 yield value 240 241 242 @warn_non_constant_time 243 def __len__(self): 244 ret = 0 245 for __ in self.items(): 246 ret += 1 247 return ret 248 249 @warn_non_constant_time 250 def __bool__(self): 251 for __ in self.items(): 252 return True 253 return False 254 __nonzero__ = __bool__ 255 256 257class InsensitivePreservingDict(collections.MutableMapping): 258 def key(self, s): 259 """Override this if you wish.""" 260 if s is not None: 261 s = s.lower() 262 return s 263 264 def __init__(self, dict=None, key=None): 265 if key is not None: 266 self.key = key 267 self.data = {} 268 if dict is not None: 269 self.update(dict) 270 271 def __repr__(self): 272 return '%s(%r)' % (self.__class__.__name__, self.data) 273 274 def fromkeys(cls, keys, s=None, dict=None, key=None): 275 d = cls(dict=dict, key=key) 276 for key in keys: 277 d[key] = s 278 return d 279 fromkeys = classmethod(fromkeys) 280 281 def __getitem__(self, k): 282 return self.data[self.key(k)][1] 283 284 def __setitem__(self, k, v): 285 self.data[self.key(k)] = (k, v) 286 287 def __delitem__(self, k): 288 del self.data[self.key(k)] 289 290 def __iter__(self): 291 return iter(self.data) 292 293 def __len__(self): 294 return len(self.data) 295 296 def items(self): 297 return self.data.values() 298 299 def items(self): 300 return self.data.values() 301 302 def keys(self): 303 L = [] 304 for (k, __) in self.items(): 305 L.append(k) 306 return L 307 308 def __reduce__(self): 309 return (self.__class__, (dict(self.data.values()),)) 310 311 312class NormalizingSet(set): 313 def __init__(self, iterable=()): 314 iterable = list(map(self.normalize, iterable)) 315 super(NormalizingSet, self).__init__(iterable) 316 317 def normalize(self, x): 318 return x 319 320 def add(self, x): 321 return super(NormalizingSet, self).add(self.normalize(x)) 322 323 def remove(self, x): 324 return super(NormalizingSet, self).remove(self.normalize(x)) 325 326 def discard(self, x): 327 return super(NormalizingSet, self).discard(self.normalize(x)) 328 329 def __contains__(self, x): 330 return super(NormalizingSet, self).__contains__(self.normalize(x)) 331 has_key = __contains__ 332 333def stackTrace(frame=None, compact=True): 334 if frame is None: 335 frame = sys._getframe() 336 if compact: 337 L = [] 338 while frame: 339 lineno = frame.f_lineno 340 funcname = frame.f_code.co_name 341 filename = os.path.basename(frame.f_code.co_filename) 342 L.append('[%s|%s|%s]' % (filename, funcname, lineno)) 343 frame = frame.f_back 344 return textwrap.fill(' '.join(L)) 345 else: 346 return traceback.format_stack(frame) 347 348def callTracer(fd=None, basename=True): 349 if fd is None: 350 fd = sys.stdout 351 def tracer(frame, event, __): 352 if event == 'call': 353 code = frame.f_code 354 lineno = frame.f_lineno 355 funcname = code.co_name 356 filename = code.co_filename 357 if basename: 358 filename = os.path.basename(filename) 359 print('%s: %s(%s)' % (filename, funcname, lineno), file=fd) 360 return tracer 361 362# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: 363