Commit bc30657b by willmcgugan@gmail.com

Fixes for backslashes on Linux issue, see Issue #139

parent 2fbb136c
...@@ -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"
...@@ -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"
......
...@@ -35,10 +35,12 @@ def _os_stat(path): ...@@ -35,10 +35,12 @@ 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):
...@@ -66,7 +68,6 @@ def _os_makedirs(name, mode=0777): ...@@ -66,7 +68,6 @@ def _os_makedirs(name, mode=0777):
os.mkdir(name, mode) os.mkdir(name, mode)
class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
"""Expose the underlying operating-system filesystem as an FS object. """Expose the underlying operating-system filesystem as an FS object.
...@@ -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
...@@ -147,10 +148,10 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -147,10 +148,10 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
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
...@@ -173,7 +174,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -173,7 +174,7 @@ 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):
...@@ -221,8 +222,8 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -221,8 +222,8 @@ 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):
...@@ -252,9 +253,9 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -252,9 +253,9 @@ 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)
...@@ -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
...@@ -322,7 +323,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS): ...@@ -322,7 +323,7 @@ class OSFS(OSFSXAttrMixin, OSFSWatchMixin, FS):
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,8 +43,6 @@ def normpath(path): ...@@ -40,8 +43,6 @@ 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('/')
...@@ -66,6 +67,21 @@ def normpath(path): ...@@ -66,6 +67,21 @@ 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.
...@@ -117,6 +133,7 @@ def isabs(path): ...@@ -117,6 +133,7 @@ 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.
...@@ -163,7 +180,7 @@ def pathjoin(*paths): ...@@ -163,7 +180,7 @@ def pathjoin(*paths):
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,6 +190,7 @@ def pathjoin(*paths): ...@@ -173,6 +190,7 @@ 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.
...@@ -185,6 +203,7 @@ def pathcombine(path1, path2): ...@@ -185,6 +203,7 @@ def pathcombine(path1, path2):
""" """
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.
......
...@@ -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"),
("/", "/", "/"), ("/", "/", "/"),
......
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