summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaurent Bachelier <laurent@bachelier.name>2011-12-28 18:34:59 +0100
committerLaurent Bachelier <laurent@bachelier.name>2011-12-28 18:44:10 +0100
commit4fae7e8c201bb15e81c2a0b41739f3be5b18edb8 (patch)
treee9a274712d5b24d6d92f4f00b970ff2dd5794a81
parentMove the encode to/decode from strings in separate methods (diff)
downloadffs-4fae7e8c201bb15e81c2a0b41739f3be5b18edb8.tar.xz
Preliminary support of lists
The list uses a Dict to manage the files. Support is complete but will need a few enhancements.
-rw-r--r--ffs.py67
-rw-r--r--test.py85
2 files changed, 147 insertions, 5 deletions
diff --git a/ffs.py b/ffs.py
index e8c2e47..6caa4bf 100644
--- a/ffs.py
+++ b/ffs.py
@@ -1,4 +1,4 @@
-from collections import MutableMapping
+from collections import MutableMapping, MutableSequence
import os
from fnmatch import fnmatchcase
import shutil
@@ -27,6 +27,55 @@ class Router(object):
return cls
+class DictList(MutableSequence):
+ def __init__(self, root, cls):
+ self.dct = Dict(root, Router({'*': cls}))
+
+ def __getitem__(self, index):
+ index = self._get_idx(index)
+ return self.dct[str(index)]
+
+ def __len__(self):
+ return len(self.dct)
+
+ def __setitem__(self, index, value):
+ index = self._get_idx(index, write=True)
+ self.dct[str(index)] = value
+
+ def __delitem__(self, index):
+ size = len(self)
+ index = self._get_idx(index, write=True)
+ for i in xrange(index, size - 1):
+ # TODO too slow, move files (with a special Dict method?) instead
+ # TODO not atomic / hard to recover
+ self.dct[str(i)] = self.dct[str(i + 1)]
+ del self.dct[str(size - 1)]
+
+ def insert(self, index, value):
+ size = len(self)
+ index = self._get_idx(index, write=True, insert=True)
+ for i in xrange(size, index, -1):
+ # TODO too slow, move files (with a special Dict method?) instead
+ # TODO not atomic / hard to recover
+ self.dct[str(i)] = self.dct[str(i - 1)]
+ self.dct[str(index)] = value
+
+ def _get_idx(self, index, write=False, insert=False):
+ if not isinstance(index, (int, long)):
+ raise TypeError('list indices must be integers, not str')
+ size = len(self)
+ if index < 0:
+ index = size + index
+ if insert:
+ if index > size:
+ index = size
+ elif index > (size - 1) or index < 0:
+ if write:
+ raise IndexError('list assignment index out of range')
+ raise IndexError('list index out of range')
+ return index
+
+
class Dict(MutableMapping):
def __init__(self, root, router):
self.root = root
@@ -41,6 +90,8 @@ class Dict(MutableMapping):
cls = self._get_cls(key)
if isinstance(cls, Router):
return Dict(self._get_path(key), cls)
+ elif isinstance(cls, list):
+ return DictList(self._get_path(key), cls[0])
else:
with open(self._get_path(key), 'rb') as f:
data = f.read()
@@ -69,7 +120,7 @@ class Dict(MutableMapping):
def __delitem__(self, key):
cls = self._get_cls(key)
- if isinstance(cls, Router):
+ if isinstance(cls, Router) or isinstance(cls, list):
shutil.rmtree(self._get_path(key))
else:
os.unlink(self._get_path(key))
@@ -83,9 +134,17 @@ class Dict(MutableMapping):
del self[key]
os.mkdir(self._get_path(key))
if len(value):
- lst = self[key]
+ dct = self[key]
for k, v in value.iteritems():
- lst[k] = v
+ dct[k] = v
+ elif isinstance(cls, list) and isinstance(value, (DictList, list)):
+ if key in self:
+ del self[key]
+ os.mkdir(self._get_path(key))
+ if len(value):
+ lst = self[key]
+ for v in value:
+ lst.append(v)
else:
if not isinstance(value, cls):
raise ValueError("%s is not a %s." % (repr(value), cls.__name__))
diff --git a/test.py b/test.py
index 4baa0e8..bf08464 100644
--- a/test.py
+++ b/test.py
@@ -1,4 +1,4 @@
-from ffs import Router, Dict, RouterError
+from ffs import Router, Dict, DictList, RouterError
from tempfile import mkdtemp
import os
import shutil
@@ -146,3 +146,86 @@ class FfsTest(TestCase):
lst2 = Dict(self.root, Router(lulz=str))
assert lst2['lulz'] == "lol"
assert isinstance(lst2['lulz'], basestring)
+
+ def test_DictList(self):
+ rtr = Router(looool=[str])
+ lst1 = Dict(self.root, rtr)
+ self.assertRaises(KeyError, lst1.__getitem__, 'looool')
+ os.mkdir(os.path.join(self.root, 'looool'))
+ assert isinstance(lst1['looool'], DictList)
+ dl1 = lst1['looool']
+ # validate index types (mimics list)
+ self.assertRaises(TypeError, dl1.__getitem__, 'lulz')
+ # no elements, can't do anything (mimics list)
+ self.assertRaises(IndexError, dl1.__getitem__, 0)
+ self.assertRaises(IndexError, dl1.__setitem__, 0, 'a')
+ self.assertRaises(IndexError, dl1.__delitem__, 0)
+ assert len(dl1) == 0
+ # add an element
+ dl1.append('b')
+ assert len(dl1) == 1
+ assert dl1[0] == 'b'
+ # alter the element
+ dl1[0] = 'c'
+ assert dl1[0] == 'c'
+ # check it's the same with a new DictList instance
+ dl2 = lst1['looool']
+ assert dl1 is not dl2
+ assert dl2[0] == 'c'
+ # check order
+ dl1.append('d')
+ dl1.append('e')
+ assert dl1[0] == 'c'
+ assert dl1[1] == 'd'
+ assert dl1[2] == 'e'
+ assert len(dl1) == 3
+ # deletion
+ del dl1[1]
+ assert dl1[0] == 'c'
+ assert dl1[1] == 'e'
+ assert len(dl1) == 2
+ self.assertRaises(IndexError, dl1.__delitem__, 2)
+ del dl1[1]
+ assert dl1[0] == 'c'
+ assert len(dl1) == 1
+ del dl1[0]
+ assert len(dl1) == 0
+ # insertion
+ dl1.insert(0, 'a')
+ dl1.insert(0, 'b')
+ dl1.insert(0, 'c')
+ assert dl1[0] == 'c'
+ assert dl1[1] == 'b'
+ assert dl1[2] == 'a'
+ assert len(dl1) == 3
+ del dl1[2]
+ dl1.insert(1, 'd')
+ assert dl1[0] == 'c'
+ assert dl1[1] == 'd'
+ assert dl1[2] == 'b'
+ assert len(dl1) == 3
+ dl1.insert(42, 'e')
+ assert dl1[3] == 'e'
+ assert len(dl1) == 4
+ dl1.insert(3, 'f')
+ assert dl1[3] == 'f'
+ assert dl1[4] == 'e'
+ assert len(dl1) == 5
+ # negative indexes
+ assert dl1[-1] == dl1[4]
+ assert dl1[-2] == dl1[3]
+ assert dl1[-3] == dl1[2]
+ assert dl1[-4] == dl1[1]
+ assert dl1[-5] == dl1[0]
+ self.assertRaises(IndexError, dl1.__getitem__, -6)
+ # erase/copy whole list
+ del lst1['looool']
+ assert 'looool' not in lst1
+ lst1['looool'] = ['x', 'y']
+ assert len(lst1['looool']) == 2
+ assert len(dl1) == 2
+ assert dl1[0] == 'x'
+ assert dl1[1] == 'y'
+ lst1['looool'] = []
+ assert 'looool' in lst1
+ assert len(dl1) == 0