Eliminate make_map in favor of a simpler map property.
[invirt/packages/python-routefs.git] / routefs / __init__.py
index 5af78a5..8d620e6 100644 (file)
@@ -5,10 +5,6 @@ lets you focus on the directory tree instead of the system calls.
 RouteFS uses the Routes library developed for Pylons. URLs were
 inspired by filesystems, and now you can have filesystems inspired by
 URLs.
-
-When developing a descendent of RouteFS, any methods defined in that
-class are considered "controllers", and receive any other parameters
-specified by the URL as keyword arguments.
 """
 
 import fuse
@@ -35,37 +31,25 @@ class RouteStat(fuse.Stat):
         self.st_mtime = 0
         self.st_ctime = 0
 
-class RouteMeta(type):
-    """
-    Metaclass to calculate controller methods
-    
-    Routes needs to be pre-seeded with a list of "controllers". For
-    all descendents of RouteFS, the list of controllers is defined to
-    be any non-private methods of the class that were not in the
-    RouteFS class.
-    """
-    def __init__(cls, classname, bases, dict_):
-        super(RouteMeta, cls).__init__(classname, bases, dict_)
-        if bases != (fuse.Fuse,):
-            new_funcs = set(dict_.keys()).difference(dir(RouteFS))
-            cls.controllers([func for func in new_funcs \
-                                 if not func.startswith('_')])
-
 class RouteFS(fuse.Fuse):
     """
     RouteFS: Web 2.0 for filesystems
+    
+    Any method that will be used as the controller in a Routes mapping
+    (either by explicitly specifying the controller or by using the
+    ':controller' variable) must be added to RouteFS.controllers
     """
-    __metaclass__ = RouteMeta
+    controllers = []
     def __init__(self, *args, **kwargs):
         super(RouteFS, self).__init__(*args, **kwargs)
         
-        self.map = self.make_map()
-        self.map.create_regs(self.controller_list)
-        
-    def make_map(self):
+        self.map.create_regs(self.controllers)
+    
+    @property
+    def map(self):
         """
-        This method should be overridden by descendents of RouteFS to
-        define the routing for the filesystem
+        This property should be overridden by descendents of RouteFS
+        to define the routing for the filesystem
         """
         m = routes.Mapper()
         
@@ -73,19 +57,21 @@ class RouteFS(fuse.Fuse):
         
         return m
     
-    @classmethod
-    def controllers(cls, lst):
-        cls.controller_list = lst
-    
     def _get_file(self, path):
         """
         Find the filesystem entry object for a given path
         """
         match = self.map.match(path)
         if match is None:
-            return
+            return NoEntry()
         controller = match.pop('controller')
         result = getattr(self, controller)(**match)
+        if result is None:
+            return NoEntry()
+        if type(result) is str:
+            result = File(result)
+        if type(result) is list:
+            result = Directory(result)
         return result
     
     def readdir(self, path, offset):
@@ -93,12 +79,7 @@ class RouteFS(fuse.Fuse):
         If the path referred to is a directory, return the elements of
         that diectory
         """
-        obj = self._get_file(path)
-        if type(obj) is not Directory:
-            return
-        else:
-            for member in ['.', '..'] + obj:
-                yield fuse.Direntry(str(member))
+        return self._get_file(path).readdir(offset)
     
     def getattr(self, path):
         """
@@ -107,49 +88,53 @@ class RouteFS(fuse.Fuse):
         The stat information for a directory, symlink, or file is
         predetermined based on which it is.
         """
-        obj = self._get_file(path)
-        if obj is None:
-            return -errno.ENOENT
-        
-        st = RouteStat()
-        if type(obj) is Directory:
-            st.st_mode = stat.S_IFDIR | 0755
-            st.st_nlink = 2
-        elif type(obj) is Symlink:
-            st.st_mode = stat.S_IFLNK | 0777
-            st.st_nlink = 1
-            st.st_size = len(obj)
-        else:
-            st.st_mode = stat.S_IFREG | 0444
-            st.st_nlink = 1
-            st.st_size = len(obj)
-        
-        return st
+        return self._get_file(path).getattr()
     
     def read(self, path, length, offset):
         """
         If the path specified is a file, return the requested portion
         of the file
         """
-        obj = self._get_file(path)
-        if obj is None:
-            return -errno.ENOENT
-        elif type(obj) in (Directory, Symlink):
-            return -errno.EINVAL
-        else:
-            return obj[offset:offset + length]
+        return self._get_file(path).read(length, offset)
     
     def readlink(self, path):
         """
         If the path specified is a symlink, return the target
         """
-        obj = self._get_file(path)
-        if type(obj) is not Symlink:
-            return -errno.EINVAL
-        else:
-            return obj
+        return self._get_file(path).readlink()
+    
+    def write(self, path, buf, offset):
+        """
+        If the path specified is a file, call the appropriate member 
+        on the file
+        """
+        return self._get_file(path).write(buf, offset)
+
+class TreeKey(object):
+    def getattr(self):
+        return -errno.EINVAL
+    def readdir(self, offset):
+        return -errno.EINVAL
+    def read(self, length, offset):
+        return -errno.EINVAL
+    def readlink(self):
+        return -errno.EINVAL
+    def write(self, length, offset):
+        return -errno.EINVAL
+
+class NoEntry(TreeKey):
+    def getattr(self):
+        return -errno.ENOENT
+    def readdir(self, offset):
+        return -errno.ENOENT
+    def read(self, length, offset):
+        return -errno.ENOENT
+    def readlink(self):
+        return -errno.ENOENT
+    def write(self, length, offset):
+        return -errno.ENOENT
 
-class TreeEntry(object):
+class TreeEntry(TreeKey):
     default_mode = 0444
     
     def __new__(cls, contents, mode=None):
@@ -170,18 +155,48 @@ class Directory(TreeEntry, list):
     """
     default_mode = 0555
 
+    def getattr(self):
+        st = RouteStat()
+        st.st_mode = stat.S_IFDIR | self.mode
+        st.st_nlink = 2
+        return st
+
+    def readdir(self, offset):
+        for member in ['.', '..'] + self:
+            yield fuse.Direntry(str(member))
+
 class Symlink(TreeEntry, str):
     """
     A dummy class representing something that should be a symlink
     """
     default_mode = 0777
 
+    def getattr(self):
+        st = RouteStat()
+        st.st_mode = stat.S_IFLNK | self.mode
+        st.st_nlink = 1
+        st.st_size = len(self)
+        return st
+
+    def readlink(self):
+        return self
+
 class File(TreeEntry, str):
     """
     A dummy class representing something that should be a file
     """
     default_mode = 0444
 
+    def getattr(self):
+        st = RouteStat()
+        st.st_mode = stat.S_IFREG | self.mode
+        st.st_nlink = 1
+        st.st_size = len(self)
+        return st
+
+    def read(self, length, offset):
+        return self[offset:offset + length]
+
 def main(cls):
     """
     A convenience function for initializing a RouteFS filesystem
@@ -189,7 +204,7 @@ def main(cls):
     server = cls(version="%prog " + fuse.__version__,
                  usage=fuse.Fuse.fusage,
                  dash_s_do='setsingle')
-    server.parse(errex=1)
+    server.parse(values=server, errex=1)
     server.main()
 
 from dictfs import DictFS