""" module with a base subversion path object. """ import os, sys, time, re, string import py from py.__.path import common ALLOWED_CHARS = "_ -/\\=$.~" #add characters as necessary when tested if sys.platform == "win32": ALLOWED_CHARS += ":" ALLOWED_CHARS_HOST = ALLOWED_CHARS + '@:' def _getsvnversion(ver=[]): try: return ver[0] except IndexError: v = py.process.cmdexec("svn -q --version") v.strip() v = '.'.join(v.split('.')[:2]) ver.append(v) return v def _escape_helper(text): text = str(text) if py.std.sys.platform != 'win32': text = str(text).replace('$', '\\$') return text def _check_for_bad_chars(text, allowed_chars=ALLOWED_CHARS): for c in str(text): if c.isalnum(): continue if c in allowed_chars: continue return True return False #_______________________________________________________________ class SvnPathBase(common.FSPathBase): """ Base implementation for SvnPath implementations. """ sep = '/' def _geturl(self): return self.strpath url = property(_geturl, None, None, "url of this svn-path.") def __str__(self): """ return a string representation (including rev-number) """ return self.strpath def __hash__(self): return hash(self.strpath) def new(self, **kw): """ create a modified version of this path. A 'rev' argument indicates a new revision. the following keyword arguments modify various path parts: http://host.com/repo/path/file.ext |-----------------------| dirname |------| basename |--| purebasename |--| ext """ obj = object.__new__(self.__class__) obj.rev = kw.get('rev', self.rev) dirname, basename, purebasename, ext = self._getbyspec( "dirname,basename,purebasename,ext") if 'basename' in kw: if 'purebasename' in kw or 'ext' in kw: raise ValueError("invalid specification %r" % kw) else: pb = kw.setdefault('purebasename', purebasename) ext = kw.setdefault('ext', ext) if ext and not ext.startswith('.'): ext = '.' + ext kw['basename'] = pb + ext kw.setdefault('dirname', dirname) kw.setdefault('sep', self.sep) if kw['basename']: obj.strpath = "%(dirname)s%(sep)s%(basename)s" % kw else: obj.strpath = "%(dirname)s" % kw return obj def _getbyspec(self, spec): """ get specified parts of the path. 'arg' is a string with comma separated path parts. The parts are returned in exactly the order of the specification. you may specify the following parts: http://host.com/repo/path/file.ext |-----------------------| dirname |------| basename |--| purebasename |--| ext """ res = [] parts = self.strpath.split(self.sep) for name in spec.split(','): name = name.strip() if name == 'dirname': res.append(self.sep.join(parts[:-1])) elif name == 'basename': res.append(parts[-1]) else: basename = parts[-1] i = basename.rfind('.') if i == -1: purebasename, ext = basename, '' else: purebasename, ext = basename[:i], basename[i:] if name == 'purebasename': res.append(purebasename) elif name == 'ext': res.append(ext) else: raise NameError, "Don't know part %r" % name return res def __eq__(self, other): """ return true if path and rev attributes each match """ return (str(self) == str(other) and (self.rev == other.rev or self.rev == other.rev)) def __ne__(self, other): return not self == other def join(self, *args): """ return a new Path (with the same revision) which is composed of the self Path followed by 'args' path components. """ if not args: return self args = tuple([arg.strip(self.sep) for arg in args]) parts = (self.strpath, ) + args newpath = self.__class__(self.sep.join(parts), self.rev) return newpath def propget(self, name): """ return the content of the given property. """ value = self._propget(name) return value def proplist(self): """ list all property names. """ content = self._proplist() return content def listdir(self, fil=None, sort=None): """ list directory contents, possibly filter by the given fil func and possibly sorted. """ if isinstance(fil, str): fil = common.fnmatch(fil) nameinfo_seq = self._listdir_nameinfo() if len(nameinfo_seq) == 1: name, info = nameinfo_seq[0] if name == self.basename and info.kind == 'file': #if not self.check(dir=1): raise py.error.ENOTDIR(self) paths = self._make_path_tuple(nameinfo_seq) if fil or sort: paths = filter(fil, paths) paths = isinstance(paths, list) and paths or list(paths) if callable(sort): paths.sort(sort) elif sort: paths.sort() return paths def info(self): """ return an Info structure with svn-provided information. """ parent = self.dirpath() nameinfo_seq = parent._listdir_nameinfo() bn = self.basename for name, info in nameinfo_seq: if name == bn: return info raise py.error.ENOENT(self) def size(self): """ Return the size of the file content of the Path. """ return self.info().size def mtime(self): """ Return the last modification time of the file. """ return self.info().mtime # shared help methods def _escape(self, cmd): return _escape_helper(cmd) def _make_path_tuple(self, nameinfo_seq): """ return a tuple of paths from a nameinfo-tuple sequence. """ #assert self.rev is not None, "revision of %s should not be None here" % self res = [] for name, info in nameinfo_seq: child = self.join(name) res.append(child) return tuple(res) def _childmaxrev(self): """ return maximum revision number of childs (or self.rev if no childs) """ rev = self.rev for name, info in self._listdir_nameinfo(): rev = max(rev, info.created_rev) return rev #def _getlatestrevision(self): # """ return latest repo-revision for this path. """ # url = self.strpath # path = self.__class__(url, None) # # # we need a long walk to find the root-repo and revision # while 1: # try: # rev = max(rev, path._childmaxrev()) # previous = path # path = path.dirpath() # except (IOError, process.cmdexec.Error): # break # if rev is None: # raise IOError, "could not determine newest repo revision for %s" % self # return rev class Checkers(common.FSCheckers): def dir(self): try: return self.path.info().kind == 'dir' except py.error.Error: return self._listdirworks() def _listdirworks(self): try: self.path.listdir() except py.error.ENOENT: return False else: return True def file(self): try: return self.path.info().kind == 'file' except py.error.ENOENT: return False def exists(self): try: return self.path.info() except py.error.ENOENT: return self._listdirworks() def parse_apr_time(timestr): i = timestr.rfind('.') if i == -1: raise ValueError, "could not parse %s" % timestr timestr = timestr[:i] parsedtime = time.strptime(timestr, "%Y-%m-%dT%H:%M:%S") return time.mktime(parsedtime) class PropListDict(dict): """ a Dictionary which fetches values (InfoSvnCommand instances) lazily""" def __init__(self, path, keynames): dict.__init__(self, [(x, None) for x in keynames]) self.path = path def __getitem__(self, key): value = dict.__getitem__(self, key) if value is None: value = self.path.propget(key) dict.__setitem__(self, key, value) return value def fixlocale(): if sys.platform != 'win32': return 'LC_ALL=C ' return '' # some nasty chunk of code to solve path and url conversion and quoting issues ILLEGAL_CHARS = '* | \ / : < > ? \t \n \x0b \x0c \r'.split(' ') if os.sep in ILLEGAL_CHARS: ILLEGAL_CHARS.remove(os.sep) ISWINDOWS = sys.platform == 'win32' _reg_allow_disk = re.compile(r'^([a-z]\:\\)?[^:]+$', re.I) def _check_path(path): illegal = ILLEGAL_CHARS[:] sp = path.strpath if ISWINDOWS: illegal.remove(':') if not _reg_allow_disk.match(sp): raise ValueError('path may not contain a colon (:)') for char in sp: if char not in string.printable or char in illegal: raise ValueError('illegal character %r in path' % (char,)) def path_to_fspath(path, addat=True): _check_path(path) sp = path.strpath if addat and path.rev != -1: sp = '%s@%s' % (sp, path.rev) elif addat: sp = '%s@HEAD' % (sp,) return sp def url_from_path(path): fspath = path_to_fspath(path, False) quote = py.std.urllib.quote if ISWINDOWS: match = _reg_allow_disk.match(fspath) fspath = fspath.replace('\\', '/') if match.group(1): fspath = '/%s%s' % (match.group(1).replace('\\', '/'), quote(fspath[len(match.group(1)):])) else: fspath = quote(fspath) else: fspath = quote(fspath) if path.rev != -1: fspath = '%s@%s' % (fspath, path.rev) else: fspath = '%s@HEAD' % (fspath,) return 'file://%s' % (fspath,)