Commit bc30657b by willmcgugan@gmail.com

Fixes for backslashes on Linux issue, see Issue #139

parent 2fbb136c
...@@ -16,15 +16,15 @@ if platform.system() == 'Windows': ...@@ -16,15 +16,15 @@ if platform.system() == 'Windows':
try: try:
## {{{ http://code.activestate.com/recipes/440694/ (r3) ## {{{ http://code.activestate.com/recipes/440694/ (r3)
from ctypes import windll, create_string_buffer from ctypes import windll, create_string_buffer
# stdin handle is -10 # stdin handle is -10
# stdout handle is -11 # stdout handle is -11
# stderr handle is -12 # stderr handle is -12
h = windll.kernel32.GetStdHandle(-12) h = windll.kernel32.GetStdHandle(-12)
csbi = create_string_buffer(22) csbi = create_string_buffer(22)
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
if res: if res:
import struct import struct
(bufx, bufy, curx, cury, wattr, (bufx, bufy, curx, cury, wattr,
...@@ -36,9 +36,9 @@ if platform.system() == 'Windows': ...@@ -36,9 +36,9 @@ if platform.system() == 'Windows':
return sizex, sizey return sizex, sizey
except: except:
return 80, 25 return 80, 25
else: else:
def getTerminalSize(): def getTerminalSize():
def ioctl_GWINSZ(fd): def ioctl_GWINSZ(fd):
try: try:
...@@ -57,10 +57,10 @@ else: ...@@ -57,10 +57,10 @@ else:
except: except:
pass pass
if cr: if cr:
return int(cr[1]), int(cr[0]) return int(cr[1]), int(cr[0])
try: try:
h, w = os.popen("stty size", "r").read().split() h, w = os.popen("stty size", "r").read().split()
return int(w), int(h) return int(w), int(h)
except: except:
pass pass
return 80, 25 return 80, 25
...@@ -71,11 +71,11 @@ def _unicode(text): ...@@ -71,11 +71,11 @@ def _unicode(text):
return text return text
class Command(object): class Command(object):
usage = '' usage = ''
version = '' version = ''
def __init__(self, usage='', version=''): def __init__(self, usage='', version=''):
self.output_file = sys.stdout self.output_file = sys.stdout
self.error_file = sys.stderr self.error_file = sys.stderr
self.encoding = getattr(self.output_file, 'encoding', 'utf-8') or 'utf-8' self.encoding = getattr(self.output_file, 'encoding', 'utf-8') or 'utf-8'
...@@ -87,24 +87,24 @@ class Command(object): ...@@ -87,24 +87,24 @@ class Command(object):
else: else:
self.terminal_width = 80 self.terminal_width = 80
self.name = self.__class__.__name__.lower() self.name = self.__class__.__name__.lower()
def is_terminal(self): def is_terminal(self):
try: try:
return self.output_file.isatty() return self.output_file.isatty()
except AttributeError: except AttributeError:
return False return False
def wrap_dirname(self, dirname): def wrap_dirname(self, dirname):
if not self.terminal_colors: if not self.terminal_colors:
return dirname return dirname
return '\x1b[1;34m%s\x1b[0m' % dirname return '\x1b[1;34m%s\x1b[0m' % dirname
def wrap_error(self, msg): def wrap_error(self, msg):
if not self.terminal_colors: if not self.terminal_colors:
return msg return msg
return '\x1b[31m%s\x1b[0m' % msg return '\x1b[31m%s\x1b[0m' % msg
def wrap_filename(self, fname): def wrap_filename(self, fname):
fname = _unicode(fname) fname = _unicode(fname)
if not self.terminal_colors: if not self.terminal_colors:
return fname return fname
...@@ -116,28 +116,28 @@ class Command(object): ...@@ -116,28 +116,28 @@ class Command(object):
if isdotfile(fname): if isdotfile(fname):
fname = '\x1b[33m%s\x1b[0m' % fname fname = '\x1b[33m%s\x1b[0m' % fname
return fname return fname
def wrap_faded(self, text): def wrap_faded(self, text):
text = _unicode(text) text = _unicode(text)
if not self.terminal_colors: if not self.terminal_colors:
return text return text
return u'\x1b[2m%s\x1b[0m' % text return u'\x1b[2m%s\x1b[0m' % text
def wrap_link(self, text): def wrap_link(self, text):
if not self.terminal_colors: if not self.terminal_colors:
return text return text
return u'\x1b[1;33m%s\x1b[0m' % text return u'\x1b[1;33m%s\x1b[0m' % text
def wrap_strong(self, text): def wrap_strong(self, text):
if not self.terminal_colors: if not self.terminal_colors:
return text return text
return u'\x1b[1m%s\x1b[0m' % text return u'\x1b[1m%s\x1b[0m' % text
def wrap_table_header(self, name): def wrap_table_header(self, name):
if not self.terminal_colors: if not self.terminal_colors:
return name return name
return '\x1b[1;32m%s\x1b[0m' % name return '\x1b[1;32m%s\x1b[0m' % name
def highlight_fsurls(self, text): def highlight_fsurls(self, text):
if not self.terminal_colors: if not self.terminal_colors:
return text return text
...@@ -146,13 +146,13 @@ class Command(object): ...@@ -146,13 +146,13 @@ class Command(object):
fs_url = matchobj.group(0) fs_url = matchobj.group(0)
return self.wrap_link(fs_url) return self.wrap_link(fs_url)
return re.sub(re_fs, repl, text) return re.sub(re_fs, repl, text)
def open_fs(self, fs_url, writeable=False, create_dir=False): def open_fs(self, fs_url, writeable=False, create_dir=False):
fs, path = opener.parse(fs_url, writeable=writeable, create_dir=create_dir) fs, path = opener.parse(fs_url, writeable=writeable, create_dir=create_dir)
fs.cache_hint(True) fs.cache_hint(True)
return fs, path return fs, path
def expand_wildcard(self, fs, path): def expand_wildcard(self, fs, path):
if path is None: if path is None:
return [], [] return [], []
pathname, resourcename = pathsplit(path) pathname, resourcename = pathsplit(path)
...@@ -161,35 +161,35 @@ class Command(object): ...@@ -161,35 +161,35 @@ class Command(object):
wildcard=resourcename, wildcard=resourcename,
absolute=True, absolute=True,
dirs_only=True) dirs_only=True)
file_paths = fs.listdir(pathname, file_paths = fs.listdir(pathname,
wildcard=resourcename, wildcard=resourcename,
absolute=True, absolute=True,
files_only=True) files_only=True)
return dir_paths, file_paths return dir_paths, file_paths
else: else:
if fs.isdir(path): if fs.isdir(path):
#file_paths = fs.listdir(path, #file_paths = fs.listdir(path,
# absolute=True) # absolute=True)
return [path], [] return [path], []
return [], [path] return [], [path]
def get_resources(self, fs_urls, dirs_only=False, files_only=False, single=False): def get_resources(self, fs_urls, dirs_only=False, files_only=False, single=False):
fs_paths = [self.open_fs(fs_url) for fs_url in fs_urls] fs_paths = [self.open_fs(fs_url) for fs_url in fs_urls]
resources = [] resources = []
for fs, path in fs_paths: for fs, path in fs_paths:
if path and iswildcard(path): if path and iswildcard(path):
if not files_only: if not files_only:
dir_paths = fs.listdir(wildcard=path, dirs_only=True) dir_paths = fs.listdir(wildcard=path, dirs_only=True)
for path in dir_paths: for path in dir_paths:
resources.append([fs, path, True]) resources.append([fs, path, True])
if not dirs_only: if not dirs_only:
file_paths = fs.listdir(wildcard=path, files_only=True) file_paths = fs.listdir(wildcard=path, files_only=True)
for path in file_paths: for path in file_paths:
resources.append([fs, path, False]) resources.append([fs, path, False])
else: else:
path = path or '/' path = path or '/'
is_dir = fs.isdir(path) is_dir = fs.isdir(path)
...@@ -199,47 +199,47 @@ class Command(object): ...@@ -199,47 +199,47 @@ class Command(object):
elif files_only and not is_dir: elif files_only and not is_dir:
resources.append(resource) resources.append(resource)
elif dirs_only and is_dir: elif dirs_only and is_dir:
resources.append(resource) resources.append(resource)
if single: if single:
break break
return resources return resources
def ask(self, msg): def ask(self, msg):
return raw_input('%s: %s ' % (self.name, msg)) return raw_input('%s: %s ' % (self.name, msg))
def text_encode(self, text): def text_encode(self, text):
if not isinstance(text, unicode): if not isinstance(text, unicode):
text = text.decode('ascii', 'replace') text = text.decode('ascii', 'replace')
text = text.encode(self.encoding, 'replace') text = text.encode(self.encoding, 'replace')
return text return text
def output(self, msgs, verbose=False): def output(self, msgs, verbose=False):
if verbose and not self.options.verbose: if verbose and not self.options.verbose:
return return
if isinstance(msgs, basestring): if isinstance(msgs, basestring):
msgs = (msgs,) msgs = (msgs,)
for msg in msgs: for msg in msgs:
self.output_file.write(self.text_encode(msg)) self.output_file.write(self.text_encode(msg))
def output_table(self, table, col_process=None, verbose=False): def output_table(self, table, col_process=None, verbose=False):
if verbose and not self.verbose: if verbose and not self.verbose:
return return
if col_process is None: if col_process is None:
col_process = {} col_process = {}
max_row_widths = defaultdict(int) max_row_widths = defaultdict(int)
for row in table: for row in table:
for col_no, col in enumerate(row): for col_no, col in enumerate(row):
max_row_widths[col_no] = max(max_row_widths[col_no], len(col)) max_row_widths[col_no] = max(max_row_widths[col_no], len(col))
lines = [] lines = []
for row in table: for row in table:
out_col = [] out_col = []
for col_no, col in enumerate(row): for col_no, col in enumerate(row):
...@@ -248,12 +248,12 @@ class Command(object): ...@@ -248,12 +248,12 @@ class Command(object):
td = col_process[col_no](td) td = col_process[col_no](td)
out_col.append(td) out_col.append(td)
lines.append(self.text_encode('%s\n' % ' '.join(out_col).rstrip())) lines.append(self.text_encode('%s\n' % ' '.join(out_col).rstrip()))
self.output(''.join(lines)) self.output(''.join(lines))
def error(self, *msgs): def error(self, *msgs):
for msg in msgs: for msg in msgs:
self.error_file.write('%s: %s' % (self.name, self.text_encode(msg))) self.error_file.write('%s: %s' % (self.name, self.text_encode(msg)))
def get_optparse(self): def get_optparse(self):
optparse = OptionParser(usage=self.usage, version=self.version) optparse = OptionParser(usage=self.usage, version=self.version)
optparse.add_option('--debug', dest='debug', action="store_true", default=False, optparse.add_option('--debug', dest='debug', action="store_true", default=False,
...@@ -265,29 +265,29 @@ class Command(object): ...@@ -265,29 +265,29 @@ class Command(object):
optparse.add_option('--fs', dest='fs', action='append', type="string", optparse.add_option('--fs', dest='fs', action='append', type="string",
help="import an FS opener e.g --fs foo.bar.MyOpener", metavar="OPENER") help="import an FS opener e.g --fs foo.bar.MyOpener", metavar="OPENER")
return optparse return optparse
def list_openers(self): def list_openers(self):
opener_table = [] opener_table = []
for fs_opener in opener.openers.itervalues(): for fs_opener in opener.openers.itervalues():
names = fs_opener.names names = fs_opener.names
desc = getattr(fs_opener, 'desc', '') desc = getattr(fs_opener, 'desc', '')
opener_table.append((names, desc)) opener_table.append((names, desc))
opener_table.sort(key = lambda r:r[0]) opener_table.sort(key = lambda r:r[0])
def wrap_line(text): def wrap_line(text):
lines = text.split('\n') lines = text.split('\n')
for line in lines: for line in lines:
words = [] words = []
line_len = 0 line_len = 0
for word in line.split(): for word in line.split():
if word == '*': if word == '*':
word = ' *' word = ' *'
if line_len + len(word) > self.terminal_width: if line_len + len(word) > self.terminal_width:
self.output((self.highlight_fsurls(' '.join(words)), '\n')) self.output((self.highlight_fsurls(' '.join(words)), '\n'))
del words[:] del words[:]
line_len = 0 line_len = 0
words.append(word) words.append(word)
...@@ -295,73 +295,73 @@ class Command(object): ...@@ -295,73 +295,73 @@ class Command(object):
if words: if words:
self.output(self.highlight_fsurls(' '.join(words))) self.output(self.highlight_fsurls(' '.join(words)))
self.output('\n') self.output('\n')
for names, desc in opener_table: for names, desc in opener_table:
self.output(('-' * self.terminal_width, '\n')) self.output(('-' * self.terminal_width, '\n'))
proto = ', '.join([n+'://' for n in names]) proto = ', '.join([n+'://' for n in names])
self.output((self.wrap_dirname('[%s]' % proto), '\n\n')) self.output((self.wrap_dirname('[%s]' % proto), '\n\n'))
if not desc.strip(): if not desc.strip():
desc = "No information available" desc = "No information available"
wrap_line(desc) wrap_line(desc)
self.output('\n') self.output('\n')
def run(self): def run(self):
parser = self.get_optparse() parser = self.get_optparse()
options, args = parser.parse_args() options, args = parser.parse_args()
self.options = options self.options = options
if options.listopeners: if options.listopeners:
self.list_openers() self.list_openers()
return 0 return 0
ilocals = {} ilocals = {}
if options.fs: if options.fs:
for import_opener in options.fs: for import_opener in options.fs:
module_name, opener_class = import_opener.rsplit('.', 1) module_name, opener_class = import_opener.rsplit('.', 1)
try: try:
opener_module = __import__(module_name, globals(), ilocals, [opener_class], -1) opener_module = __import__(module_name, globals(), ilocals, [opener_class], -1)
except ImportError: except ImportError:
self.error("Unable to import opener %s\n" % import_opener) self.error("Unable to import opener %s\n" % import_opener)
return 0 return 0
new_opener = getattr(opener_module, opener_class) new_opener = getattr(opener_module, opener_class)
try: try:
if not issubclass(new_opener, Opener): if not issubclass(new_opener, Opener):
self.error('%s is not an fs.opener.Opener\n' % import_opener) self.error('%s is not an fs.opener.Opener\n' % import_opener)
return 0 return 0
except TypeError: except TypeError:
self.error('%s is not an opener class\n' % import_opener) self.error('%s is not an opener class\n' % import_opener)
return 0 return 0
if options.verbose: if options.verbose:
self.output('Imported opener %s\n' % import_opener) self.output('Imported opener %s\n' % import_opener)
opener.add(new_opener) opener.add(new_opener)
args = [unicode(arg, sys.getfilesystemencoding()) for arg in args] args = [unicode(arg, sys.getfilesystemencoding()) for arg in args]
self.verbose = options.verbose self.verbose = options.verbose
try: try:
return self.do_run(options, args) or 0 return self.do_run(options, args) or 0
except FSError, e: except FSError, e:
self.error(self.wrap_error(unicode(e)) + '\n') self.error(self.wrap_error(unicode(e)) + '\n')
if options.debug: if options.debug:
raise raise
return 1 return 1
except KeyboardInterrupt: except KeyboardInterrupt:
if self.is_terminal(): if self.is_terminal():
self.output("\n") self.output("\n")
return 0 return 0
except SystemExit: except SystemExit:
return 0 return 0
except Exception, e: except Exception, e:
self.error(self.wrap_error('Error - %s\n' % unicode(e))) self.error(self.wrap_error('Error - %s\n' % unicode(e)))
if options.debug: if options.debug:
raise raise
return 1 return 1
if __name__ == "__main__": if __name__ == "__main__":
command = Command() command = Command()
sys.exit(command.run()) sys.exit(command.run())
\ No newline at end of file
...@@ -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