# Copyright (C) 2003-2007 Swedish Institute of Computer Science. # # Please refer to the file named LICENSE in the same directory as this # file for licensing information. # # $Id: utils.py,v 1.1 2007/08/21 14:41:01 fros4943 Exp $ import errno import math import operator import os import popen2 import re import string import shutil import sys import tempfile import threading import traceback import types import chakana.command import chakana.error from chakana.debug import * try: global False global True _ = False except NameError: False = 0 True = 1 class DefaultDict(dict): def __init__(self, defaultCreator, initialiser = (), inject = 0): dict.__init__(self, initialiser) if callable(defaultCreator): self._defaultCreator = defaultCreator else: self._defaultCreator = lambda k: defaultCreator self._inject = inject def __getitem__(self, key): try: return dict.__getitem__(self, key) except KeyError, err: value = self._defaultCreator(key) if self._inject: self[key] = value return self[key] else: return value class UnbufferedFile: def __init__(self, fileObj): self._fileObj = fileObj def __getattr__(self, attribute): return getattr(self._fileObj, attribute) def write(self, what): self._fileObj.write(what) self._fileObj.flush() def copyFile(src, dest): debug(Event, "cp " + src + " " + dest) srcFile = file(src) destFile = file(dest, "w") destFile.write(srcFile.read()) srcFile.close() destFile.close() def unique(list): ret = [] inList = {} for elem in list: if not elem in inList: ret.append(elem) inList[elem] = 1 return ret def unorderedEqual(listOne, listTwo, predicate = lambda x, y: x == y): if len(listOne) != len(listTwo): return 0 list1 = list(listOne) list2 = list(listTwo) while list1 != []: found = 0 for index in range(len(list2)): if predicate(list1[0], list2[index]): del list1[0] del list2[index] found = 1 break if not found: return 0 return 1 def reduceSequences(tupleSeq, oper = operator.add): """Return a list, where the item at position i is the reduced value of all items at position i.""" if len(tupleSeq) == 0: return [] return [reduce(oper, [s[i] for s in tupleSeq]) for i in range(len(tupleSeq[0]))] def listRemove(theList, predicate): """Remove all items that match predicate in theList.""" if not callable(predicate): return listRemove(theList, lambda x: x == predicate) else: index = 0 while index < len(theList): if predicate(theList[index]): del theList[index] else: index += 1 return theList def logn(n, x): return math.log(x) / math.log(n) def longRange(* args): """As range, but works with long integers.""" if len(args) == 1: start = 0L stop = args[0] step = 1L elif len(args) == 2: (start, stop) = args step = 1L else: (start, stop, step) = args if step == 0: raise ValueError("longRange() arg 3 must not be zero") ret = [] while cmp(start, stop) == cmp(0, step): ret.append(start) start += step return ret integerSuffixes = ("", "K", "M", "G", "T") def abbreviateInteger(i, factor = 1024, minimise = 0): suf = 0 while suf < len(integerSuffixes) - 1 and \ ((i > 9999) or (minimise and (i != 0) and (i % factor == 0))): suf += 1 i /= factor return str(i) + integerSuffixes[suf] def readSuffixedInt(intString, factor = 1024): if intString[-1].isalpha(): return int(intString[: -1]) * ( factor ** list(integerSuffixes).index(intString[-1].upper())) else: return int(intString) def normaliseIndex(index, seq): """Return a non-negative index relating to a sequence, or negative if index < - len(seq).""" if index < 0: return len(seq) + index else: return index def truncateIndex(index, seq): """Return a non-negative index relating to a sequence, less than or equal to the length of the sequence.""" ret = normaliseIndex(index, seq) if ret < 0: return 0 length = len(seq) if ret > length: return length return ret class LazySlice: """Representation of slice into other sequence.""" def __init__(self, seq, start = None, stop = None, step = None): self._sequence = seq if step is None: self._step = 1 else: self._step = step if self._step == 0: # Provoke ValueError [][0:1:0] if self._step > 0: startDefault = 0 stopDefault = len(seq) indexShift = 0 else: startDefault = len(seq) - 1 stopDefault = -1 indexShift = 1 if start is None: self._start = startDefault else: self._start = truncateIndex(start + indexShift, seq) - indexShift if stop is None: self._stop = stopDefault else: self._stop = truncateIndex(stop + indexShift, seq) - indexShift if (self._step > 0) != (self._start < self._stop): self._stop = self._start def __getitem__(self, key): # debug(Debug2, "LS getitem " + str(key) + ": " + repr(self)) if type(key) == types.SliceType: ret = LazySlice(self, key.start, key.stop, key.step) #debug(Debug2, "LS getitem " + str(key) + " = " + # repr(ret) + ", LS: " + repr(self)) return ret index = normaliseIndex(key, self) if index < 0: # Provoke IndexError [][1] seqIndex = self._start + index * self._step if self._step > 0: if seqIndex >= self._stop: [][1] elif seqIndex <= self._stop: [][1] ret = self._sequence[seqIndex] #debug(Debug2, "LS getitem " + str(key) + " (" + str(seqIndex) + ") = " + # str(ret) + ", LS: " + repr(self)) return ret def __len__(self): return (abs(self._stop - self._start) + abs(self._step) - 1) / \ abs(self._step) def __str__(self): return str(list(self)) def __repr__(self): return "LazySlice(start = " + str(self._start) + ", stop = " + \ str(self._stop) + ", step = " + str(self._step) + ",\n " + \ repr(self._sequence) + ")" def getItem(sequence, key): """Obtain key in sequence. Key may be an integer or a slice object.""" if type(key) == types.SliceType: if key.step is None: return sequence[key.start : key.stop] return sequence[key.start : key.stop : key.step] return sequence[key] def binarySearch(list, value, start = 0, end = None): "Return the position where value should be inserted in a sorted list." if end is None: end = len(list) if start == end: return end middle = start + (end - start) / 2 if list[middle] < value: return binarySearch(list, value, middle + 1, end) else: return binarySearch(list, value, start, middle) def binaryFind(list, value, *args, ** kwArgs): "Return position of value in sorted list, or None if not found." position = binarySearch(list, value, *args, ** kwArgs) if (position == len(list)) or (list[position] != value): return None else: return position def compactIntListRepr(input, sort = 0): if sort: list = input[:] list.sort() else: list = input if __debug__: tmp = list[:] tmp.sort() assert(tmp == list) index = 0 ret = "[" sep = "" compact = lambda l, i: (i + 1 < len(l)) and (l[i] + 1 == l[i + 1]) while index < len(list): ret += sep + str(list[index]) if compact(list, index): ret += "-" while compact(list, index): index += 1 ret += str(list[index]) index += 1 sep = "," return ret + "]" def predicatedIndex(sequence, predicate): for i in range(len(sequence)): if predicate(sequence[i]): return i raise ValueError() class ArgumentBinder: def __init__(self, function, argument, position = 0): self.__function = function self.__argument = argument self.__position = position def __call__(self, *args, **keywords): newArgs = list(args) newArgs[self.__position : self.__position] = [self.__argument] return self.__function(*newArgs, **keywords) class ArgumentTupleBinder: def __init__(self, function, arguments): self._function = function self._arguments = arguments def __call__(self, * args, ** kwArgs): return self._function(* (self._arguments + args), ** kwArgs) class KeywordArgumentBinder: def __init__(self, function, ** keywords): self._function = function self._keywords = keywords def __call__(self, * args, ** kwArgs): kw = self._keywords kw.update(kwArgs) return self._function(* args, ** kw) cppKeywords = ["asm", "do", "inline", "short", "typeid", "auto", "double", "int", "signed", "typename", "bool", "dynamic_cast", "long", "sizeof", "union", "break", "else", "mutable", "static", "unsigned", "case", "enum", "namespace", "static_cast", "using", "catch", "explicit", "new", "struct", "virtual", "char", "extern", "operator", "switch", "void", "class", "false", "private", "template", "volatile", "const", "float", "protected", "this", "wchar_t", "const_cast", "for", "public", "throw", "while", "continue", "friend", "register", "true", "default", "goto", "reinterpret_cast", "try", "delete", "if", "return", "typedef"] def isCWord(str): return not re.match("^[a-zA-Z_][a-zA-Z0-9_]*$", str) is None def isCppIdentifier(str): return isCWord(str) and (str not in cppKeywords) def find(seq, predicate = lambda x: x): for item in seq: if predicate(item): return item return None def allBest(seq, comparator = cmp): if len(seq) < 2: return seq seq = seq[:] ret = [seq.pop()] for item in seq: c = comparator(item, ret[0]) if c < 0: ret = [item] elif c == 0: ret.append(item) return ret def readFile(fileName): f = open(fileName) try: ret = f.read() return ret finally: f.close() def writeFile(fileName, contents): f = open(fileName, "w") try: f.write(contents) finally: f.close() def writeFileAtomic(fileName, contents, suffix = ".tmp"): tmpFile = fileName + suffix writeFile(tmpFile, contents) try: os.rename(tmpFile, fileName) except: if os.path.exists(fileName) and os.path.exists(tmpFile): os.remove(tmpFile) raise class LineIndexed: """Read-only file object indexed by line number. The file object must support seeking.""" def __init__(self, fileName, offset = 0): if type(fileName) == type(""): self._file = file(fileName) else: self._file = fileName self._offset = offset self._length = None self._lineOffsets = [0] def close(self): self._file.close() def __del__(self): self.close() def _seekForward(self, index): if self._length == 0: raise IndexError() for index in range(len(self._lineOffsets), index + 1): if self._lineOffsets[-1] == self._length: raise IndexError() self._file.seek(self._lineOffsets[-1] + self._offset) line = self._file.readline() if line == "": self._length = self._lineOffsets[-1] else: self._lineOffsets.append(self._lineOffsets[-1] + len(line)) def __getitem__(self, key): if type(key) == types.SliceType: ret = LazySlice(self, key.start, key.stop, key.step) return ret if key < 0: positiveLength = len(self) + key if positiveLength < 0: raise IndexError() return self[positiveLength] self._seekForward(key) offset = self._lineOffsets[key] if offset == self._length: raise IndexError() self._file.seek(offset + self._offset) return self._file.readline() def __len__(self): if self._length == 0: return 0 while self._lineOffsets[-1] != self._length: self._seekForward(len(self._lineOffsets)) return len(self._lineOffsets) - 1 def lineOffset(self, index): self._seekForward(index) return self._lineOffsets[index] def __str__(self): return str(list(self)) def __repr__(self): return "LineIndexed(offset = " + str(self._offset) + ", length = " + \ str(self._length) + ", lineOffsets: " + \ str(self._lineOffsets) + ")" def getLine(fileName, count): fileObj = file(fileName) if count < 0: return fileObj.readlines()[count] for i in range(count + 1): ret = fileObj.readline() if ret == "": raise IndexError() return ret def conditionalJoin(list, separator = " ", predicate = lambda e: e != ""): if len(list) == 0: return "" rest = conditionalJoin(list[1:], separator, predicate) if not predicate(rest): return list[0] if not predicate(list[0]): return rest else: return list[0] + separator + rest def pathComponents(path): (head, tail) = os.path.split(path) if tail == "": if head == "": return [] if head == "/": return ["/"] else: return pathComponents(head) else: return pathComponents(head) + [tail] def commonFirstElements(listOfLists): if len(listOfLists) == 0: return [] if len(listOfLists) == 1: return listOfLists[0] if (len(listOfLists[0]) == 0) or (len(listOfLists[1]) == 0) or \ (listOfLists[0][0] != listOfLists[1][0]): return [] else: return commonFirstElements([ [listOfLists[0][0]] + commonFirstElements([listOfLists[0][1:], listOfLists[1][1:]])] + listOfLists[2:]) def commonPrefix(pathList): """Similar to os.path.commonprefix, but works on path components instead of individual characters.""" assert(not isinstance(pathList, str)) components = commonFirstElements(map(pathComponents, pathList)) if components == []: return "" else: return os.path.join(* components) def pathIsBelow(path, directory): """Check if path lies below directory.""" return commonPrefix([path, directory]) == directory def realPath(path): """Similar to os.path.realpath, but handles amd gracefully.""" ret = os.path.realpath(path) for binDir in os.environ["PATH"].split(":") + ["/usr/sbin", "/sbin"]: amq = os.path.join(binDir, "amq") if os.path.isfile(amq): try: output = chakana.command.output(amq) except chakana.error.CommandFailed, err: debug(Debug, str(err)) # Assume amd is not running return ret for line in output.splitlines(): amdDir = line.split()[0] mountDir = line.split()[-1] if mountDir[0] == "/": match = re.match('^(' + re.escape(line) + ')(/$)', ret) if match: return amdDir + ret[len(line) :] return ret return ret def removeTree(path, ignoreErrors = False): debug(Debug, "rm -rf " + path) if os.path.isdir(path): shutil.rmtree(path, ignore_errors = ignoreErrors) def makeSymlink(value, dest, force = True, debugLevel = MinorEvent, dryRun = False): if os.path.islink(dest): if os.readlink(dest) == value: debug(Debug, "Link " + dest + " already points to " + value) return elif force: debug(debugLevel, "Removing " + dest) if not dryRun: os.remove(dest) else: raise OSError((errno.EEXIST, "Link already exists", dest)) absValue = os.path.join(os.path.dirname(dest), value) if (not dryRun) and (not force) and (not os.path.isfile(absValue)): raise OSError((errno.ENOENT, "Link destination does not exist", absDest)) debug(debugLevel, "Linking " + dest + " to " + value) if not dryRun: os.symlink(value, dest) def copyTree(src, dst, symlinks = False, predicate = lambda path: 1, preserveTimes = False): """Similar to shutil.copytree, but allows existing destination and passes exceptions.""" names = filter(predicate, os.listdir(src)) if not os.path.isdir(dst): os.mkdir(dst) for name in names: srcname = os.path.join(src, name) dstname = os.path.join(dst, name) try: if symlinks and os.path.islink(srcname): linkto = os.readlink(srcname) os.symlink(linkto, dstname) elif os.path.isdir(srcname): copyTree(srcname, dstname, symlinks, predicate, preserveTimes) else: if preserveTimes: shutil.copy2(srcname, dstname) else: shutil.copy(srcname, dstname) # XXX What about devices, sockets etc.? except (IOError, os.error), why: debug(Error, "Can't copy %s to %s: %s" % (`srcname`, `dstname`, str(why))) raise def makeDirsSafe(dir, mode = 0777): """Similar to os.makedirs, but does not fail if another process is making the directory simultaneously.""" while not os.path.isdir(dir): try: os.makedirs(dir, mode) except OSError, err: if err.errno != errno.EEXIST: raise def typeAssert(variable, type): assert(isinstance(variable, type)) def intervalsOverlap(range1, range2): if range1 > range2: return intervalsOverlap(range2, range1) if (range1[0] == range1[1]) or (range2[0] == range2[1]): return 0 return range2[0] < range1[1] class LexicalDistance: def __init__(self, caseChange = 1, whiteSpaceChange = 2, whiteSpaceRemoval = 3, ampersand = 5, other = 10, whiteSpace = " _-'\""): self.__caseChange = caseChange self.__whiteSpaceChange = whiteSpaceChange self.__whiteSpaceRemoval = whiteSpaceRemoval self.__ampersand = ampersand self.__other = other self.__whiteSpace = whiteSpace def removeWhiteSpace(self, str1, str2, limit): if str1[0] in self.__whiteSpace: if limit > self.__whiteSpaceRemoval: return self(str1[1:], str2, limit - self.__whiteSpaceRemoval) + \ self.__whiteSpaceRemoval return limit def changeWhiteSpace(self, str1, str2, limit): if (str1[0] in self.__whiteSpace) and (str2[0] in self.__whiteSpace): if limit > self.__whiteSpaceChange: return self(str1[1:], str2[1:], limit - self.__whiteSpaceChange) + \ self.__whiteSpaceChange return limit def changeCase(self, str1, str2, limit): if str1[0].upper() == str2[0].upper(): if limit > self.__caseChange: return self(str1[1:], str2[1:], limit - self.__caseChange) + \ self.__caseChange return limit def changeAmpersand(self, str1, str2, limit): if (str1[0] == "&") and (str2[:3].lower() == "and"): if limit > self.__ampersand: return self(str1[1:], str2[3:], limit - self.__ampersand) + \ self.__ampersand return limit def removeOther(self, str1, str2, limit): if limit > self.__other: return self(str1[1:], str2, limit - self.__other) + self.__other return limit def changeOther(self, str1, str2, limit): if limit > self.__other: return self(str1[1:], str2[1:], limit - self.__other) + self.__other return limit def __call__(self, str1, str2, limit = 50): ret = limit if str1 == "": if str2 == "": return 0 ret = self.removeWhiteSpace(str2, str1, ret) ret = self.removeOther(str2, str1, ret) elif str2 == "": ret = self(str2, str1, ret) else: if str1[0] == str2[0]: ret = self(str1[1:], str2[1:], ret) else: ret = self.removeWhiteSpace(str1, str2, ret) ret = self.removeWhiteSpace(str2, str1, ret) ret = self.changeWhiteSpace(str1, str2, ret) ret = self.changeCase(str1, str2, ret) ret = self.changeAmpersand(str1, str2, ret) ret = self.changeAmpersand(str2, str1, ret) ret = self.changeOther(str1, str2, ret) ret = self.removeOther(str1, str2, ret) ret = self.removeOther(str2, str1, ret) assert(ret <= limit) return ret class TryOperation: def __init__(self, operation, description = None): self.__operation = operation if description is None: self.__description = str(operation) else: self.__description = description def __call__(self, * args, ** kwargs): try: self.__operation(* args, ** kwargs) except KeyboardInterrupt: raise except: import debug as d d.debug(d.Error, self.__description + " failed\n" + d.exceptionDump(sys.exc_info())) def lineDirective(): fileInfo = traceback.extract_stack(None, 2)[0] return "#line " + repr(fileInfo[1] + 1) + " \"" + fileInfo[0] + "\"" try: mkstemp = tempfile.mkstemp except AttributeError: def mkstemp(suffix = "", prefix = None, dir = None, text = 0): if prefix is None: prefix = tempfile.gettempprefix() fileName = tempfile.mktemp(suffix) if dir is None: dir = os.path.dirname(fileName) newFileName = os.path.join(dir, prefix + os.path.basename(fileName)) if text: flags = "w" else: flags = "wb" return (file(newFileName, flags), newFileName)