import os, os.path, stat class PathSet(object) : """Represents an ordered set of unique normalized file paths. The paths may be the subject to user (~) and variable ($) expansion. This class fulfills the sequence contract (i.e. len(), x[], iterating etc.). """ def __init__ (self, *args, **kwds) : """Construct a set. Any number of either string-like objects or the sequences of thereof is accepted. The following keywords are recognized: userExpand -- (default True) varExpand -- (default True) """ self.paths = [] try : self.varExpand = kwds["varExpand"] except KeyError : self.varExpand = True try : self.userExpand = kwds["userExpand"] except KeyError : self.userExpand = True for x in args : self.append(x) def __iter__(self) : return self.paths.__iter__() def __len__(self) : return len(self.paths) def __str__(self) : paths = self.paths if len(paths) > 1 : s = str(paths[0]) for x in paths[1:] : s += ":" + str(x) elif len(paths) > 0 : s = str(paths[0]) else : s = "" return s def __contains__ (self, path) : return self.adopt(path) in self.paths def __getitem__(self, index) : return self.paths[index] def __add__(self, other) : x = self.__class__() x.append(self) x.append(other) return x def __iadd__(self, other) : self.append(other) return self def adopt(self, path) : """Validates the specified path and converts it to a form suitable for inclusion into the set. Must either return the path or raise ValueError if an inappropriate path was given. """ if self.userExpand and path.startswith("~") : path = os.path.expanduser(path) if self.varExpand and "$" in path : path = os.path.expandvars(path) path = os.path.normpath(path) if path in self.paths : raise ValueError else : return path def append(self, arg) : if isinstance(arg, str) : try : self.paths.append(self.adopt(arg)) except ValueError : pass else : for x in arg : self.append(str(x)) # WARNING : a recursive call, stay alert class ValidPathSet(PathSet) : """Represents the PathSet of the valid paths. The symbolic links are respected and have the preference over the real directories they point to. """ def adopt(self, path) : path = super(ValidPathSet, self).adopt(path) try : st = os.stat(path) for xst in self.stats : if os.path.samestat(st, xst) : if os.path.islink(path) and not os.path.islink(self.stats[xst]) : # FIXME : inefficiency # In case of real path followed by the symlink to it # the latter has the preference self.paths[self.paths.index(self.stats[xst])] = path del self.stats[xst]; self.stats[st] = path # In all other cases the path is discarded raise ValueError # To prevent the appending of the path self.stats[st] = path return path except OSError : raise ValueError def __init__(self, *args, **kwds) : self.stats = {} super(ValidPathSet, self).__init__(*args, **kwds)