Commit bc30657b by willmcgugan@gmail.com

Fixes for backslashes on Linux issue, see Issue #139

parent 2fbb136c
...@@ -24,7 +24,7 @@ __all__ = ['FSError', ...@@ -24,7 +24,7 @@ __all__ = ['FSError',
'NoMetaError', 'NoMetaError',
'NoPathURLError', 'NoPathURLError',
'ResourceNotFoundError', 'ResourceNotFoundError',
'ResourceInvalidError', 'ResourceInvalidError',
'DestinationExistsError', 'DestinationExistsError',
'DirectoryNotEmptyError', 'DirectoryNotEmptyError',
'ParentDirectoryMissingError', 'ParentDirectoryMissingError',
...@@ -42,6 +42,10 @@ from fs.path import * ...@@ -42,6 +42,10 @@ from fs.path import *
from fs.local_functools import wraps from fs.local_functools import wraps
class InvalidPathError(Exception):
pass
class FSError(Exception): class FSError(Exception):
"""Base exception class for the FS module.""" """Base exception class for the FS module."""
default_message = "Unspecified error" default_message = "Unspecified error"
...@@ -81,7 +85,7 @@ class PathError(FSError): ...@@ -81,7 +85,7 @@ class PathError(FSError):
def __init__(self,path="",**kwds): def __init__(self,path="",**kwds):
self.path = path self.path = path
super(PathError,self).__init__(**kwds) super(PathError,self).__init__(**kwds)
class OperationFailedError(FSError): class OperationFailedError(FSError):
"""Base exception class for errors associated with a specific operation.""" """Base exception class for errors associated with a specific operation."""
...@@ -184,6 +188,7 @@ class ResourceLockedError(ResourceError): ...@@ -184,6 +188,7 @@ class ResourceLockedError(ResourceError):
"""Exception raised when a resource can't be used because it is locked.""" """Exception raised when a resource can't be used because it is locked."""
default_message = "Resource is locked: %(path)s" default_message = "Resource is locked: %(path)s"
class NoMMapError(ResourceError): class NoMMapError(ResourceError):
"""Exception raise when getmmap fails to create a mmap""" """Exception raise when getmmap fails to create a mmap"""
default_message = "Can't get mmap for %(path)s" default_message = "Can't get mmap for %(path)s"
......
...@@ -32,13 +32,15 @@ from fs.osfs.watch import OSFSWatchMixin ...@@ -32,13 +32,15 @@ from fs.osfs.watch import OSFSWatchMixin
@convert_os_errors @convert_os_errors
def _os_stat(path): def _os_stat(path):
"""Replacement for os.stat that raises FSError subclasses.""" """Replacement for os.stat that raises FSError subclasses."""
return os.stat(path) return os.stat(path)
@convert_os_errors @convert_os_errors
def _os_mkdir(name, mode=0777): def _os_mkdir(name, mode=0777):
"""Replacement for os.mkdir that raises FSError subclasses.""" """Replacement for os.mkdir that raises FSError subclasses."""
return os.mkdir(name,mode) return os.mkdir(name, mode)
@convert_os_errors @convert_os_errors
def _os_makedirs(name, mode=0777): def _os_makedirs(name, mode=0777):
...@@ -64,7 +66,6 @@ def _os_makedirs(name, mode=0777): ...@@ -64,7 +66,6 @@ def _os_makedirs(name, mode=0777):
if tail == os.curdir: if tail == os.curdir:
return return
os.mkdir(name, mode) os.mkdir(name, mode)
class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
...@@ -74,7 +75,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -74,7 +75,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
filesystem of the OS. Most of its methods simply defer to the matching filesystem of the OS. Most of its methods simply defer to the matching
methods in the os and os.path modules. methods in the os and os.path modules.
""" """
_meta = { 'thread_safe' : True, _meta = { 'thread_safe' : True,
'network' : False, 'network' : False,
'virtual' : False, 'virtual' : False,
...@@ -90,7 +91,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -90,7 +91,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
""" """
Creates an FS object that represents the OS Filesystem under a given root path Creates an FS object that represents the OS Filesystem under a given root path
:param root_path: The root OS path :param root_path: The root OS path
:param thread_synchronize: If True, this object will be thread-safe by use of a threading.Lock object :param thread_synchronize: If True, this object will be thread-safe by use of a threading.Lock object
:param encoding: The encoding method for path strings :param encoding: The encoding method for path strings
:param create: If True, then root_path will be created if it doesn't already exist :param create: If True, then root_path will be created if it doesn't already exist
...@@ -114,7 +115,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -114,7 +115,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
if root_path.startswith("\\\\"): if root_path.startswith("\\\\"):
root_path = u"\\\\?\\UNC\\" + root_path[2:] root_path = u"\\\\?\\UNC\\" + root_path[2:]
else: else:
root_path = u"\\\\?" + root_path root_path = u"\\\\?" + root_path
# If it points at the root of a drive, it needs a trailing slash. # If it points at the root of a drive, it needs a trailing slash.
if len(root_path) == 6 and not root_path.endswith("\\"): if len(root_path) == 6 and not root_path.endswith("\\"):
root_path = root_path + "\\" root_path = root_path + "\\"
...@@ -126,9 +127,9 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -126,9 +127,9 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
pass pass
if not os.path.exists(root_path): if not os.path.exists(root_path):
raise ResourceNotFoundError(root_path,msg="Root directory does not exist: %(path)s") raise ResourceNotFoundError(root_path, msg="Root directory does not exist: %(path)s")
if not os.path.isdir(root_path): if not os.path.isdir(root_path):
raise ResourceInvalidError(root_path,msg="Root path is not a directory: %(path)s") raise ResourceInvalidError(root_path, msg="Root path is not a directory: %(path)s")
self.root_path = root_path self.root_path = root_path
self.dir_mode = dir_mode self.dir_mode = dir_mode
...@@ -137,20 +138,20 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -137,20 +138,20 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
def __repr__(self): def __repr__(self):
return "<OSFS: %r>" % self.root_path return "<OSFS: %r>" % self.root_path
def __unicode__(self): def __unicode__(self):
return u"<OSFS: %s>" % self.root_path return u"<OSFS: %s>" % self.root_path
def _decode_path(self, p): def _decode_path(self, p):
if isinstance(p, unicode): if isinstance(p, unicode):
return p return p
return p.decode(self.encoding, 'replace') return p.decode(self.encoding, 'replace')
def getsyspath(self, path, allow_none=False): def getsyspath(self, path, allow_none=False):
path = relpath(normpath(path)).replace("/",os.sep) path = relpath(normpath(path)).replace("/", os.sep)
path = os.path.join(self.root_path, path) path = os.path.join(self.root_path, path)
if not path.startswith(self.root_path): if not path.startswith(self.root_path):
raise PathError(path,msg="OSFS given path outside root: %(path)s") raise PathError(path, msg="OSFS given path outside root: %(path)s")
path = self._decode_path(path) path = self._decode_path(path)
return path return path
...@@ -159,11 +160,11 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -159,11 +160,11 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
This basically the reverse of getsyspath(). If the path does not This basically the reverse of getsyspath(). If the path does not
refer to a location within this filesystem, ValueError is raised. refer to a location within this filesystem, ValueError is raised.
:param path: a system path :param path: a system path
:returns: a path within this FS object :returns: a path within this FS object
:rtype: string :rtype: string
""" """
path = os.path.normpath(os.path.abspath(path)) path = os.path.normpath(os.path.abspath(path))
path = self._decode_path(path) path = self._decode_path(path)
...@@ -173,11 +174,11 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -173,11 +174,11 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
if not prefix.endswith(os.path.sep): if not prefix.endswith(os.path.sep):
prefix += os.path.sep prefix += os.path.sep
if not os.path.normcase(path).startswith(prefix): if not os.path.normcase(path).startswith(prefix):
raise ValueError("path not within this FS: %s (%s)" % (os.path.normcase(path),prefix)) raise ValueError("path not within this FS: %s (%s)" % (os.path.normcase(path), prefix))
return normpath(path[len(self.root_path):]) return normpath(path[len(self.root_path):])
def getmeta(self, meta_name, default=NoDefaultMeta): def getmeta(self, meta_name, default=NoDefaultMeta):
if meta_name == 'free_space': if meta_name == 'free_space':
if platform.system() == 'Windows': if platform.system() == 'Windows':
try: try:
...@@ -204,11 +205,11 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -204,11 +205,11 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
else: else:
stat = os.statvfs(self.root_path) stat = os.statvfs(self.root_path)
return stat.f_blocks * stat.f_bsize return stat.f_blocks * stat.f_bsize
return super(OSFS, self).getmeta(meta_name, default) return super(OSFS, self).getmeta(meta_name, default)
@convert_os_errors @convert_os_errors
def open(self, path, mode="r", **kwargs): def open(self, path, mode="r", **kwargs):
mode = ''.join(c for c in mode if c in 'rwabt+') mode = ''.join(c for c in mode if c in 'rwabt+')
sys_path = self.getsyspath(path) sys_path = self.getsyspath(path)
try: try:
...@@ -221,25 +222,25 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -221,25 +222,25 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
raise raise
@convert_os_errors @convert_os_errors
def setcontents(self, path, contents, chunk_size=64*1024): def setcontents(self, path, contents, chunk_size=64 * 1024):
return super(OSFS,self).setcontents(path, contents, chunk_size) return super(OSFS, self).setcontents(path, contents, chunk_size)
@convert_os_errors @convert_os_errors
def exists(self, path): def exists(self, path):
return _exists(self.getsyspath(path)) return _exists(self.getsyspath(path))
@convert_os_errors @convert_os_errors
def isdir(self, path): def isdir(self, path):
return _isdir(self.getsyspath(path)) return _isdir(self.getsyspath(path))
@convert_os_errors @convert_os_errors
def isfile(self, path): def isfile(self, path):
return _isfile(self.getsyspath(path)) return _isfile(self.getsyspath(path))
@convert_os_errors @convert_os_errors
def listdir(self, path="./", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): def listdir(self, path="./", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False):
_decode_path = self._decode_path _decode_path = self._decode_path
paths = [_decode_path(p) for p in os.listdir(self.getsyspath(path))] paths = [_decode_path(p) for p in os.listdir(self.getsyspath(path))]
return self._listdir_helper(path, paths, wildcard, full, absolute, dirs_only, files_only) return self._listdir_helper(path, paths, wildcard, full, absolute, dirs_only, files_only)
@convert_os_errors @convert_os_errors
...@@ -252,16 +253,16 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -252,16 +253,16 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
_os_mkdir(sys_path, self.dir_mode) _os_mkdir(sys_path, self.dir_mode)
except DestinationExistsError: except DestinationExistsError:
if self.isfile(path): if self.isfile(path):
raise ResourceInvalidError(path,msg="Cannot create directory, there's already a file of that name: %(path)s") raise ResourceInvalidError(path, msg="Cannot create directory, there's already a file of that name: %(path)s")
if not allow_recreate: if not allow_recreate:
raise DestinationExistsError(path,msg="Can not create a directory that already exists (try allow_recreate=True): %(path)s") raise DestinationExistsError(path, msg="Can not create a directory that already exists (try allow_recreate=True): %(path)s")
except ResourceNotFoundError: except ResourceNotFoundError:
raise ParentDirectoryMissingError(path) raise ParentDirectoryMissingError(path)
@convert_os_errors @convert_os_errors
def remove(self, path): def remove(self, path):
sys_path = self.getsyspath(path) sys_path = self.getsyspath(path)
try: try:
os.remove(sys_path) os.remove(sys_path)
except OSError, e: except OSError, e:
if e.errno == errno.EACCES and sys.platform == "win32": if e.errno == errno.EACCES and sys.platform == "win32":
...@@ -275,7 +276,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -275,7 +276,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
raise raise
@convert_os_errors @convert_os_errors
def removedir(self, path, recursive=False, force=False): def removedir(self, path, recursive=False, force=False):
sys_path = self.getsyspath(path) sys_path = self.getsyspath(path)
if force: if force:
for path2 in self.listdir(path, absolute=True, files_only=True): for path2 in self.listdir(path, absolute=True, files_only=True):
...@@ -297,7 +298,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -297,7 +298,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
if recursive: if recursive:
try: try:
if dirname(path) not in ('', '/'): if dirname(path) not in ('', '/'):
self.removedir(dirname(path),recursive=True) self.removedir(dirname(path), recursive=True)
except DirectoryNotEmptyError: except DirectoryNotEmptyError:
pass pass
...@@ -320,9 +321,9 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -320,9 +321,9 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
if not os.path.exists(os.path.dirname(path_dst)): if not os.path.exists(os.path.dirname(path_dst)):
raise ParentDirectoryMissingError(dst) raise ParentDirectoryMissingError(dst)
raise raise
def _stat(self,path): def _stat(self, path):
"""Stat the given path, normalising error codes.""" """Stat the given path, normalising error codes."""
sys_path = self.getsyspath(path) sys_path = self.getsyspath(path)
try: try:
...@@ -350,5 +351,3 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -350,5 +351,3 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
@convert_os_errors @convert_os_errors
def getsize(self, path): def getsize(self, path):
return self._stat(path).st_size return self._stat(path).st_size
from __future__ import unicode_literals
""" """
fs.path fs.path
======= =======
...@@ -11,22 +13,23 @@ by forward slashes and with an optional leading slash). ...@@ -11,22 +13,23 @@ by forward slashes and with an optional leading slash).
""" """
import re import re
import os
_requires_normalization = re.compile(r'/\.\.|\./|\.|//').search
_requires_normalization = re.compile(r'/\.\.|\./|\.|//|\\').search
def normpath(path): def normpath(path):
"""Normalizes a path to be in the format expected by FS objects. """Normalizes a path to be in the format expected by FS objects.
This function remove any leading or trailing slashes, collapses This function remove any leading or trailing slashes, collapses
duplicate slashes, replaces backward with forward slashes, and generally duplicate slashes, and generally tries very hard to return a new path
tries very hard to return a new path string the canonical FS format. in the canonical FS format.
If the path is invalid, ValueError will be raised. If the path is invalid, ValueError will be raised.
:param path: path to normalize :param path: path to normalize
:returns: a valid FS path :returns: a valid FS path
>>> normpath(r"foo\\bar\\baz")
'foo/bar/baz'
>>> normpath("/foo//bar/frob/../baz") >>> normpath("/foo//bar/frob/../baz")
'/foo/bar/baz' '/foo/bar/baz'
...@@ -40,15 +43,13 @@ def normpath(path): ...@@ -40,15 +43,13 @@ def normpath(path):
if path in ('', '/'): if path in ('', '/'):
return path return path
path = path.replace('\\', '/')
# An early out if there is no need to normalize this path # An early out if there is no need to normalize this path
if not _requires_normalization(path): if not _requires_normalization(path):
return path.rstrip('/') return path.rstrip('/')
components = [] components = []
append = components.append append = components.append
special = ('..', '.', '').__contains__ special = ('..', '.', '').__contains__
try: try:
for component in path.split('/'): for component in path.split('/'):
if special(component): if special(component):
...@@ -66,12 +67,27 @@ def normpath(path): ...@@ -66,12 +67,27 @@ def normpath(path):
return '/'.join(components) return '/'.join(components)
if os.sep != '/':
def ospath(path):
"""Replace path separators in an OS path if required"""
return path.replace(os.sep, '/')
else:
def ospath(path):
"""Replace path separators in an OS path if required"""
return path
def normospath(path):
"""Normalizes a path with os separators"""
return normpath(ospath)
def iteratepath(path, numsplits=None): def iteratepath(path, numsplits=None):
"""Iterate over the individual components of a path. """Iterate over the individual components of a path.
:param path: Path to iterate over :param path: Path to iterate over
:numsplits: Maximum number of splits :numsplits: Maximum number of splits
""" """
path = relpath(normpath(path)) path = relpath(normpath(path))
if not path: if not path:
...@@ -84,39 +100,40 @@ def iteratepath(path, numsplits=None): ...@@ -84,39 +100,40 @@ def iteratepath(path, numsplits=None):
def recursepath(path, reverse=False): def recursepath(path, reverse=False):
"""Returns intermediate paths from the root to the given path """Returns intermediate paths from the root to the given path
:param reverse: reverses the order of the paths :param reverse: reverses the order of the paths
>>> recursepath('a/b/c') >>> recursepath('a/b/c')
['/', u'/a', u'/a/b', u'/a/b/c'] ['/', u'/a', u'/a/b', u'/a/b/c']
""" """
if path in ('', '/'): if path in ('', '/'):
return [u'/'] return [u'/']
path = abspath(normpath(path)) + '/' path = abspath(normpath(path)) + '/'
paths = [u'/'] paths = [u'/']
find = path.find find = path.find
append = paths.append append = paths.append
pos = 1 pos = 1
len_path = len(path) len_path = len(path)
while pos < len_path: while pos < len_path:
pos = find('/', pos) pos = find('/', pos)
append(path[:pos]) append(path[:pos])
pos += 1 pos += 1
if reverse: if reverse:
return paths[::-1] return paths[::-1]
return paths return paths
def isabs(path): def isabs(path):
"""Return True if path is an absolute path.""" """Return True if path is an absolute path."""
return path.startswith('/') return path.startswith('/')
def abspath(path): def abspath(path):
"""Convert the given path to an absolute path. """Convert the given path to an absolute path.
...@@ -134,9 +151,9 @@ def relpath(path): ...@@ -134,9 +151,9 @@ def relpath(path):
This is the inverse of abspath(), stripping a leading '/' from the This is the inverse of abspath(), stripping a leading '/' from the
path if it is present. path if it is present.
:param path: Path to adjust :param path: Path to adjust
>>> relpath('/a/b') >>> relpath('/a/b')
'a/b' 'a/b'
...@@ -146,7 +163,7 @@ def relpath(path): ...@@ -146,7 +163,7 @@ def relpath(path):
def pathjoin(*paths): def pathjoin(*paths):
"""Joins any number of paths together, returning a new path string. """Joins any number of paths together, returning a new path string.
:param paths: Paths to join are given in positional arguments :param paths: Paths to join are given in positional arguments
>>> pathjoin('foo', 'bar', 'baz') >>> pathjoin('foo', 'bar', 'baz')
...@@ -160,10 +177,10 @@ def pathjoin(*paths): ...@@ -160,10 +177,10 @@ def pathjoin(*paths):
""" """
absolute = False absolute = False
relpaths = [] relpaths = []
for p in paths: for p in paths:
if p: if p:
if p[0] in '\\/': if p[0] == '/':
del relpaths[:] del relpaths[:]
absolute = True absolute = True
relpaths.append(p) relpaths.append(p)
...@@ -173,24 +190,26 @@ def pathjoin(*paths): ...@@ -173,24 +190,26 @@ def pathjoin(*paths):
path = abspath(path) path = abspath(path)
return path return path
def pathcombine(path1, path2): def pathcombine(path1, path2):
"""Joins two paths together. """Joins two paths together.
This is faster than `pathjoin`, but only works when the second path is relative, This is faster than `pathjoin`, but only works when the second path is relative,
and there are no backreferences in either path. and there are no backreferences in either path.
>>> pathcombine("foo/bar", "baz") >>> pathcombine("foo/bar", "baz")
'foo/bar/baz' 'foo/bar/baz'
""" """
return "%s/%s" % (path1.rstrip('/'), path2.lstrip('/')) return "%s/%s" % (path1.rstrip('/'), path2.lstrip('/'))
def join(*paths): def join(*paths):
"""Joins any number of paths together, returning a new path string. """Joins any number of paths together, returning a new path string.
This is a simple alias for the ``pathjoin`` function, allowing it to be This is a simple alias for the ``pathjoin`` function, allowing it to be
used as ``fs.path.join`` in direct correspondence with ``os.path.join``. used as ``fs.path.join`` in direct correspondence with ``os.path.join``.
:param paths: Paths to join are given in positional arguments :param paths: Paths to join are given in positional arguments
""" """
return pathjoin(*paths) return pathjoin(*paths)
...@@ -201,7 +220,7 @@ def pathsplit(path): ...@@ -201,7 +220,7 @@ def pathsplit(path):
This function splits a path into a pair (head, tail) where 'tail' is the This function splits a path into a pair (head, tail) where 'tail' is the
last pathname component and 'head' is all preceding components. last pathname component and 'head' is all preceding components.
:param path: Path to split :param path: Path to split
>>> pathsplit("foo/bar") >>> pathsplit("foo/bar")
...@@ -209,7 +228,7 @@ def pathsplit(path): ...@@ -209,7 +228,7 @@ def pathsplit(path):
>>> pathsplit("foo/bar/baz") >>> pathsplit("foo/bar/baz")
('foo/bar', 'baz') ('foo/bar', 'baz')
>>> pathsplit("/foo/bar/baz") >>> pathsplit("/foo/bar/baz")
('/foo/bar', 'baz') ('/foo/bar', 'baz')
...@@ -234,17 +253,17 @@ def split(path): ...@@ -234,17 +253,17 @@ def split(path):
def splitext(path): def splitext(path):
"""Splits the extension from the path, and returns the path (up to the last """Splits the extension from the path, and returns the path (up to the last
'.' and the extension). '.' and the extension).
:param path: A path to split :param path: A path to split
>>> splitext('baz.txt') >>> splitext('baz.txt')
('baz', 'txt') ('baz', 'txt')
>>> splitext('foo/bar/baz.txt') >>> splitext('foo/bar/baz.txt')
('foo/bar/baz', 'txt') ('foo/bar/baz', 'txt')
""" """
parent_path, pathname = pathsplit(path) parent_path, pathname = pathsplit(path)
if '.' not in pathname: if '.' not in pathname:
return path, '' return path, ''
...@@ -256,18 +275,18 @@ def splitext(path): ...@@ -256,18 +275,18 @@ def splitext(path):
def isdotfile(path): def isdotfile(path):
"""Detects if a path references a dot file, i.e. a resource who's name """Detects if a path references a dot file, i.e. a resource who's name
starts with a '.' starts with a '.'
:param path: Path to check :param path: Path to check
>>> isdotfile('.baz') >>> isdotfile('.baz')
True True
>>> isdotfile('foo/bar/baz') >>> isdotfile('foo/bar/baz')
True True
>>> isdotfile('foo/bar.baz'). >>> isdotfile('foo/bar.baz').
False False
""" """
return basename(path).startswith('.') return basename(path).startswith('.')
...@@ -277,15 +296,15 @@ def dirname(path): ...@@ -277,15 +296,15 @@ def dirname(path):
This is always equivalent to the 'head' component of the value returned This is always equivalent to the 'head' component of the value returned
by pathsplit(path). by pathsplit(path).
:param path: A FS path :param path: A FS path
>>> dirname('foo/bar/baz') >>> dirname('foo/bar/baz')
'foo/bar' 'foo/bar'
>>> dirname('/foo/bar') >>> dirname('/foo/bar')
'/foo' '/foo'
>>> dirname('/foo') >>> dirname('/foo')
'/' '/'
...@@ -298,15 +317,15 @@ def basename(path): ...@@ -298,15 +317,15 @@ def basename(path):
This is always equivalent to the 'tail' component of the value returned This is always equivalent to the 'tail' component of the value returned
by pathsplit(path). by pathsplit(path).
:param path: A FS path :param path: A FS path
>>> basename('foo/bar/baz') >>> basename('foo/bar/baz')
'baz' 'baz'
>>> basename('foo/bar') >>> basename('foo/bar')
'bar' 'bar'
>>> basename('foo/bar/') >>> basename('foo/bar/')
'' ''
...@@ -316,7 +335,7 @@ def basename(path): ...@@ -316,7 +335,7 @@ def basename(path):
def issamedir(path1, path2): def issamedir(path1, path2):
"""Return true if two paths reference a resource in the same directory. """Return true if two paths reference a resource in the same directory.
:param path1: An FS path :param path1: An FS path
:param path2: An FS path :param path2: An FS path
...@@ -332,15 +351,15 @@ def issamedir(path1, path2): ...@@ -332,15 +351,15 @@ def issamedir(path1, path2):
def isbase(path1, path2): def isbase(path1, path2):
p1 = forcedir(abspath(path1)) p1 = forcedir(abspath(path1))
p2 = forcedir(abspath(path2)) p2 = forcedir(abspath(path2))
return p1 == p2 or p1.startswith(p2) return p1 == p2 or p1.startswith(p2)
def isprefix(path1, path2): def isprefix(path1, path2):
"""Return true is path1 is a prefix of path2. """Return true is path1 is a prefix of path2.
:param path1: An FS path :param path1: An FS path
:param path2: An FS path :param path2: An FS path
>>> isprefix("foo/bar", "foo/bar/spam.txt") >>> isprefix("foo/bar", "foo/bar/spam.txt")
True True
>>> isprefix("foo/bar/", "foo/bar") >>> isprefix("foo/bar/", "foo/bar")
...@@ -365,7 +384,7 @@ def isprefix(path1, path2): ...@@ -365,7 +384,7 @@ def isprefix(path1, path2):
def forcedir(path): def forcedir(path):
"""Ensure the path ends with a trailing / """Ensure the path ends with a trailing /
:param path: An FS path :param path: An FS path
>>> forcedir("foo/bar") >>> forcedir("foo/bar")
...@@ -602,12 +621,12 @@ class PathMap(object): ...@@ -602,12 +621,12 @@ class PathMap(object):
_wild_chars = frozenset('*?[]!{}') _wild_chars = frozenset('*?[]!{}')
def iswildcard(path): def iswildcard(path):
"""Check if a path ends with a wildcard """Check if a path ends with a wildcard
>>> is_wildcard('foo/bar/baz.*') >>> is_wildcard('foo/bar/baz.*')
True True
>>> is_wildcard('foo/bar') >>> is_wildcard('foo/bar')
False False
""" """
assert path is not None assert path is not None
base_chars = frozenset(basename(path)) base_chars = frozenset(basename(path))
......
...@@ -14,7 +14,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -14,7 +14,7 @@ class TestPathFunctions(unittest.TestCase):
"""Testcases for FS path functions.""" """Testcases for FS path functions."""
def test_normpath(self): def test_normpath(self):
tests = [ ("\\a\\b\\c", "/a/b/c"), tests = [ ("\\a\\b\\c", "\\a\\b\\c"),
(".", ""), (".", ""),
("./", ""), ("./", ""),
("", ""), ("", ""),
...@@ -22,7 +22,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -22,7 +22,7 @@ class TestPathFunctions(unittest.TestCase):
("a/b/c", "a/b/c"), ("a/b/c", "a/b/c"),
("a/b/../c/", "a/c"), ("a/b/../c/", "a/c"),
("/","/"), ("/","/"),
(u"a/\N{GREEK SMALL LETTER BETA}\\c",u"a/\N{GREEK SMALL LETTER BETA}/c"), (u"a/\N{GREEK SMALL LETTER BETA}/c",u"a/\N{GREEK SMALL LETTER BETA}/c"),
] ]
for path, result in tests: for path, result in tests:
self.assertEqual(normpath(path), result) self.assertEqual(normpath(path), result)
...@@ -38,7 +38,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -38,7 +38,7 @@ class TestPathFunctions(unittest.TestCase):
("a/b/c", "../d", "c", "a/b/d/c"), ("a/b/c", "../d", "c", "a/b/d/c"),
("a/b/c", "../d", "/a", "/a"), ("a/b/c", "../d", "/a", "/a"),
("aaa", "bbb/ccc", "aaa/bbb/ccc"), ("aaa", "bbb/ccc", "aaa/bbb/ccc"),
("aaa", "bbb\ccc", "aaa/bbb/ccc"), ("aaa", "bbb\\ccc", "aaa/bbb\\ccc"),
("aaa", "bbb", "ccc", "/aaa", "eee", "/aaa/eee"), ("aaa", "bbb", "ccc", "/aaa", "eee", "/aaa/eee"),
("a/b", "./d", "e", "a/b/d/e"), ("a/b", "./d", "e", "a/b/d/e"),
("/", "/", "/"), ("/", "/", "/"),
...@@ -104,7 +104,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -104,7 +104,7 @@ class TestPathFunctions(unittest.TestCase):
self.assertEquals(recursepath("/hello/world/",reverse=True),["/hello/world","/hello","/"]) self.assertEquals(recursepath("/hello/world/",reverse=True),["/hello/world","/hello","/"])
self.assertEquals(recursepath("hello",reverse=True),["/hello","/"]) self.assertEquals(recursepath("hello",reverse=True),["/hello","/"])
self.assertEquals(recursepath("",reverse=True),["/"]) self.assertEquals(recursepath("",reverse=True),["/"])
def test_isdotfile(self): def test_isdotfile(self):
for path in ['.foo', for path in ['.foo',
'.svn', '.svn',
...@@ -112,14 +112,14 @@ class TestPathFunctions(unittest.TestCase): ...@@ -112,14 +112,14 @@ class TestPathFunctions(unittest.TestCase):
'foo/bar/.svn', 'foo/bar/.svn',
'/foo/.bar']: '/foo/.bar']:
self.assert_(isdotfile(path)) self.assert_(isdotfile(path))
for path in ['asfoo', for path in ['asfoo',
'df.svn', 'df.svn',
'foo/er.svn', 'foo/er.svn',
'foo/bar/test.txt', 'foo/bar/test.txt',
'/foo/bar']: '/foo/bar']:
self.assertFalse(isdotfile(path)) self.assertFalse(isdotfile(path))
def test_dirname(self): def test_dirname(self):
tests = [('foo', ''), tests = [('foo', ''),
('foo/bar', 'foo'), ('foo/bar', 'foo'),
...@@ -129,7 +129,7 @@ class TestPathFunctions(unittest.TestCase): ...@@ -129,7 +129,7 @@ class TestPathFunctions(unittest.TestCase):
('/', '/')] ('/', '/')]
for path, test_dirname in tests: for path, test_dirname in tests:
self.assertEqual(dirname(path), test_dirname) self.assertEqual(dirname(path), test_dirname)
def test_basename(self): def test_basename(self):
tests = [('foo', 'foo'), tests = [('foo', 'foo'),
('foo/bar', 'bar'), ('foo/bar', 'bar'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment