Commit 1287ce09 by rfkelly0

removed obsolute "objectree" module; use fs.path.PathMap instead

parent 01c3efb8
...@@ -70,4 +70,5 @@ ...@@ -70,4 +70,5 @@
* Added copyfile_non_atomic and movefile_non_atomic for improved performance of multi-threaded copies * Added copyfile_non_atomic and movefile_non_atomic for improved performance of multi-threaded copies
* Added ilistdir() and ilistdirinfo() methods, which are generator-based * Added ilistdir() and ilistdirinfo() methods, which are generator-based
variants of listdir() and listdirinfo(). variants of listdir() and listdirinfo().
* Removed obsolute module fs.objectree; use fs.path.PathMap instead.
...@@ -224,6 +224,7 @@ class FS(object): ...@@ -224,6 +224,7 @@ class FS(object):
The following are less common: The following are less common:
* *free_space* The free space (in bytes) available on the file system * *free_space* The free space (in bytes) available on the file system
* *total_space* The total space (in bytes) available on the file system
FS implementations may expose non-generic meta data through a self-named namespace. e.g. 'somefs.some_meta' FS implementations may expose non-generic meta data through a self-named namespace. e.g. 'somefs.some_meta'
......
...@@ -32,7 +32,7 @@ import tempfile as _tempfile ...@@ -32,7 +32,7 @@ import tempfile as _tempfile
try: try:
from cStringIO import StringIO as _StringIO from cStringIO import StringIO as _StringIO
except ImportError: except ImportError:
from StrimgIO import StringIO as _StringIO from StringIO import StringIO as _StringIO
import fs import fs
......
...@@ -218,6 +218,49 @@ class MountFS(FS): ...@@ -218,6 +218,49 @@ class MountFS(FS):
return paths return paths
@synchronize @synchronize
def ilistdir(self, path="/", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False):
fs, mount_path, delegate_path = self._delegate(path)
if fs is None:
raise ResourceNotFoundError(path)
if fs is self:
paths = self.mount_tree.names(path)
for path in self._listdir_helper(path,wildcard,full,absolute,dirs_only,files_only):
yield path
else:
paths = fs.ilistdir(delegate_path,
wildcard=wildcard,
full=False,
absolute=False,
dirs_only=dirs_only)
extra_paths = set(self.mount_tree.names(path))
if full:
pathhead = relpath(normpath(path))
def mkpath(p):
return pathjoin(pathhead,p)
elif absolute:
pathhead = abspath(normpath(path))
def mkpath(p):
return pathjoin(pathhead,p)
else:
def mkpath(p):
return p
for p in paths:
if p not in extra_paths:
yield mkpath(p)
for p in extra_paths:
if dirs_only:
if self.isdir(pathjoin(path,p)):
yield mkpath(p)
elif files_only:
if self.isfile(pathjoin(path,nm)):
yield mkpath(p)
else:
yield mkpath(p)
@synchronize
def makedir(self, path, recursive=False, allow_recreate=False): def makedir(self, path, recursive=False, allow_recreate=False):
fs, mount_path, delegate_path = self._delegate(path) fs, mount_path, delegate_path = self._delegate(path)
if fs is self or fs is None: if fs is self or fs is None:
......
class _ObjectDict(dict):
pass
class ObjectTree(object):
"""A class to facilitate the creation of tree structures."""
def __init__(self):
self.root = _ObjectDict()
def _split(self, path):
if '/' not in path:
return "", path
else:
return path.rsplit('/', 1)
def _splitpath(self, path):
return [p for p in path.split('/') if p]
def _locate(self, path):
current = self.root
for path_component in self._splitpath(path):
if type(current) is not _ObjectDict:
return None
node = current.get(path_component, None)
if node is None:
return None
current = node
return current
def __setitem__(self, path, object):
if not path:
raise IndexError("No path supplied")
current = self.root
path, name = self._split(path)
for path_component in self._splitpath(path):
node = current.get(path_component, None)
if type(node) is not _ObjectDict:
new_dict = _ObjectDict()
current[path_component] = new_dict
current = new_dict
else:
current = node
current[name] = object
def __getitem__(self, path):
node = self._locate(path)
if node is None:
raise IndexError("Path does not exist")
return node
def __delitem__(self, path):
path, name = self._split(path)
node = self._locate(path)
if node is None or type(node) is not _ObjectDict:
raise IndexError("Path does not exist")
del node[name]
def get(self, path, default):
node = self._locate(path)
if node is None:
return default
return node
def partialget(self, path, default=None):
current = self.root
partial_path = []
remaining_path = self._splitpath(path)
for path_component in remaining_path[:]:
if type(current) is not _ObjectDict:
return "/".join(partial_path), current, "/".join(remaining_path)
partial_path.append(path_component)
remaining_path.pop(0)
node = current.get(path_component, None)
if node is None:
return None, default, None
current = node
return path, current, ""
def isobject(self, path):
node = self._locate(path)
return type(node) is not _ObjectDict
def __contains__(self, path):
node = self._locate(path)
return node is not None
def __iter__(self):
return iter(self.root)
def keys(self):
return self.root.keys()
def iterkeys(self):
return self.root.iterkeys()
def items(self):
return self.root.items()
def iteritems(self):
return self.root.iteritems()
...@@ -17,7 +17,7 @@ def normpath(path): ...@@ -17,7 +17,7 @@ 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 forward with backward slashes, and generally duplicate slashes, replaces backward with forward slashes, and generally
tries very hard to return a new path string the canonical FS format. tries very hard to return a new path string the canonical FS format.
If the path is invalid, ValueError will be raised. If the path is invalid, ValueError will be raised.
...@@ -162,8 +162,16 @@ def pathjoin(*paths): ...@@ -162,8 +162,16 @@ def pathjoin(*paths):
path = abspath(path) path = abspath(path)
return path return path
# Allow pathjoin() to be used as fs.path.join()
join = pathjoin def join(*paths):
"""Joins any number of paths together, returning a new path string.
This is a simple alias for the ``pathjoin`` function, allowing it to be
used as ``fs.path.join`` in direct correspondance with ``os.path.join``.
:param paths: Paths to join are given in positional arguments
"""
return pathjoin(*paths)
def pathsplit(path): def pathsplit(path):
...@@ -189,8 +197,17 @@ def pathsplit(path): ...@@ -189,8 +197,17 @@ def pathsplit(path):
split = path.rsplit('/', 1) split = path.rsplit('/', 1)
return (split[0] or '/', split[1]) return (split[0] or '/', split[1])
# Allow pathsplit() to be used as fs.path.split()
split = pathsplit def split(path):
"""Splits a path into (head, tail) pair.
This is a simple alias for the ``pathsplit`` function, allowing it to be
used as ``fs.path.split`` in direct correspondance with ``os.path.split``.
:param path: Path to split
"""
return pathsplit(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
...@@ -337,9 +354,18 @@ def frombase(path1, path2): ...@@ -337,9 +354,18 @@ def frombase(path1, path2):
class PathMap(object): class PathMap(object):
"""Dict-like object with paths for keys. """Dict-like object with paths for keys.
A PathMap is like a dictionary where the keys are all FS paths. It allows A PathMap is like a dictionary where the keys are all FS paths. It has
various dictionary operations (e.g. listing values, clearing values) to two main advantages over a standard dictionary. First, keys are normalised
be performed on a subset of the keys sharing some common prefix, e.g.:: automatically::
>>> pm = PathMap()
>>> pm["hello/world"] = 42
>>> print pm["/hello/there/../world"]
42
Second, various dictionary operations (e.g. listing or clearing values)
can be efficiently performed on a subset of keys sharing some common
prefix::
# list all values in the map # list all values in the map
pm.values() pm.values()
......
"""
fs.tests.test_objectree: testcases for the fs objecttree module
"""
import unittest
import fs.tests
from fs import objecttree
class TestObjectTree(unittest.TestCase):
"""Testcases for the ObjectTree class."""
def test_getset(self):
ot = objecttree.ObjectTree()
ot['foo'] = "bar"
self.assertEqual(ot['foo'], 'bar')
ot = objecttree.ObjectTree()
ot['foo/bar'] = "baz"
self.assertEqual(ot['foo'], {'bar':'baz'})
self.assertEqual(ot['foo/bar'], 'baz')
del ot['foo/bar']
self.assertEqual(ot['foo'], {})
ot = objecttree.ObjectTree()
ot['a/b/c'] = "A"
ot['a/b/d'] = "B"
ot['a/b/e'] = "C"
ot['a/b/f'] = "D"
self.assertEqual(sorted(ot['a/b'].values()), ['A', 'B', 'C', 'D'])
self.assert_(ot.get('a/b/x', -1) == -1)
self.assert_('a/b/c' in ot)
self.assert_('a/b/x' not in ot)
self.assert_(ot.isobject('a/b/c'))
self.assert_(ot.isobject('a/b/d'))
self.assert_(not ot.isobject('a/b'))
left, object, right = ot.partialget('a/b/e/f/g')
self.assertEqual(left, "a/b/e")
self.assertEqual(object, "C")
self.assertEqual(right, "f/g")
...@@ -192,6 +192,32 @@ class WrapFS(FS): ...@@ -192,6 +192,32 @@ class WrapFS(FS):
return entries return entries
@rewrite_errors @rewrite_errors
def ilistdir(self, path="", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False):
kwds = dict(wildcard=wildcard,
full=full,
absolute=absolute,
dirs_only=dirs_only,
files_only=files_only)
full = kwds.pop("full",False)
absolute = kwds.pop("absolute",False)
wildcard = kwds.pop("wildcard",None)
if wildcard is None:
wildcard = lambda fn:True
elif not callable(wildcard):
wildcard_re = re.compile(fnmatch.translate(wildcard))
wildcard = lambda fn:bool (wildcard_re.match(fn))
enc_path = self._encode(path)
for e in self.wrapped_fs.ilistdir(enc_path,**kwds):
e = basename(self._decode(pathjoin(enc_path,e)))
if not wildcard(e):
continue
if full:
e = pathjoin(path,e)
elif absolute:
e = abspath(pathjoin(path,e))
yield e
@rewrite_errors
def listdirinfo(self, path="", **kwds): def listdirinfo(self, path="", **kwds):
full = kwds.pop("full",False) full = kwds.pop("full",False)
absolute = kwds.pop("absolute",False) absolute = kwds.pop("absolute",False)
...@@ -215,6 +241,27 @@ class WrapFS(FS): ...@@ -215,6 +241,27 @@ class WrapFS(FS):
return entries return entries
@rewrite_errors @rewrite_errors
def ilistdirinfo(self, path="", **kwds):
full = kwds.pop("full",False)
absolute = kwds.pop("absolute",False)
wildcard = kwds.pop("wildcard",None)
if wildcard is None:
wildcard = lambda fn:True
elif not callable(wildcard):
wildcard_re = re.compile(fnmatch.translate(wildcard))
wildcard = lambda fn:bool (wildcard_re.match(fn))
enc_path = self._encode(path)
for (nm,info) in self.wrapped_fs.ilistdirinfo(enc_path,**kwds):
nm = basename(self._decode(pathjoin(enc_path,nm)))
if not wildcard(nm):
continue
if full:
nm = pathjoin(path,nm)
elif absolute:
nm = abspath(pathjoin(path,nm))
yield (nm,info)
@rewrite_errors
def makedir(self, path, *args, **kwds): def makedir(self, path, *args, **kwds):
return self.wrapped_fs.makedir(self._encode(path),*args,**kwds) return self.wrapped_fs.makedir(self._encode(path),*args,**kwds)
......
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