degal/filesystem.py
changeset 51 0f39cb5e4b11
child 55 77abe8dca695
equal deleted inserted replaced
50:724121253d34 51:0f39cb5e4b11
       
     1 """
       
     2     Filesystem path handling
       
     3 """
       
     4 
       
     5 import os, os.path
       
     6 
       
     7 class Node (object) :
       
     8     """
       
     9         A filesystem object is basically just complicated representation of a path.
       
    10         
       
    11         On the plus side, it has a parent node and can handle unicode/binary paths.
       
    12     """
       
    13 
       
    14     # the binary name
       
    15     fsname = None
       
    16 
       
    17     # the unicode name
       
    18     name = None
       
    19     
       
    20     def decode_fsname (self, fsname) :
       
    21         """
       
    22             Decode the given raw byte string representing a filesystem name into an user-readable unicode name.
       
    23 
       
    24             XXX: currently just hardcoded as utf-8
       
    25         """
       
    26 
       
    27         return fsname.decode('utf-8', 'replace')
       
    28     
       
    29     def encode_name (self, name) :
       
    30         """
       
    31             Returns a suitable fsname for the given unicode name or strict ASCII str
       
    32 
       
    33             XXX: currently just hardcoded as utf-8
       
    34         """
       
    35         
       
    36         # this should fail for non-ASCII str
       
    37         return name.encode('utf-8')
       
    38 
       
    39     @classmethod
       
    40     def from_node (cls, node) :
       
    41         """
       
    42             Construct from a Node object
       
    43         """
       
    44 
       
    45         return cls(node.parent, node.fsname, node.name, node.config)
       
    46 
       
    47     def __init__ (self, parent, fsname=None, name=None, config=None) :
       
    48         """
       
    49             Initialize the node with a parent and both name/fsname.
       
    50 
       
    51             If not given, fsname is encoded from name, or name decoded from fsname, using encode/decode_name.
       
    52 
       
    53             If parent is given, but both fsname and name are None, then this node will be equal to the parent.
       
    54         """
       
    55 
       
    56         assert not fsname or isinstance(fsname, str)
       
    57 
       
    58         if parent and not fsname and not name :
       
    59             # no name given -> we're the same as parent
       
    60             self.parent, self.config, self.fsname, self.name = parent.parent, parent.config, parent.fsname, parent.name
       
    61 
       
    62         else :
       
    63             # store
       
    64             self.parent = parent
       
    65             
       
    66             # config, either as given, or copy from parent
       
    67             if config :
       
    68                 self.config = config
       
    69 
       
    70             else :
       
    71                 self.config = parent.config
       
    72      
       
    73             # fsname
       
    74             if fsname :
       
    75                 self.fsname = fsname
       
    76 
       
    77             else :
       
    78                 self.fsname = self.encode_name(name)
       
    79            
       
    80             # name
       
    81             if name :
       
    82                 self.name = name
       
    83 
       
    84             else :
       
    85                 self.name = self.decode_fsname(fsname)
       
    86         
       
    87     def subnode (self, name) :
       
    88         """
       
    89             Returns a Node object representing the given name behind this node.
       
    90 
       
    91             The name should either be a plain ASCII string or unicode object.
       
    92         """
       
    93         
       
    94         return Node(self, name=name)
       
    95  
       
    96     @property
       
    97     def path (self) :
       
    98         """
       
    99             Build and return the real filesystem path for this node
       
   100         """
       
   101         
       
   102         # build using parent path and our fsname
       
   103         return os.path.join(self.parent.path, self.fsname)
       
   104     
       
   105     @property
       
   106     def unicodepath (self) :
       
   107         """
       
   108             Build and return the fake unicode filesystem path for this node
       
   109         """
       
   110         
       
   111         # build using parent unicodepath and our name
       
   112         return os.path.join(self.parent.path, self.name)
       
   113    
       
   114     def exists (self) :
       
   115         """
       
   116             Tests if this node exists on the physical filesystem
       
   117         """
       
   118 
       
   119         return os.path.exists(self.path)
       
   120 
       
   121     def is_dir (self) :
       
   122         """
       
   123             Tests if this node represents a directory on the filesystem
       
   124         """
       
   125 
       
   126         return os.path.isdir(self.path)
       
   127 
       
   128     def is_file (self) :
       
   129         """
       
   130             Tests if this node represents a normal file on the filesystem
       
   131         """
       
   132 
       
   133         return os.path.isfile(self.path)
       
   134     
       
   135     def __str__ (self) :
       
   136         """
       
   137             Returns the raw filesystem path
       
   138         """
       
   139 
       
   140         return self.path
       
   141 
       
   142     def __unicode__ (self) :
       
   143         """
       
   144             Returns the human-readable path
       
   145         """
       
   146 
       
   147         return self.unicodepath
       
   148     
       
   149     def __repr__ (self) :
       
   150         """
       
   151             Returns a str representing this dir
       
   152         """
       
   153 
       
   154         return "Node(%r, %r)" % (self.parent.path, self.fsname)
       
   155     
       
   156     def __cmp__ (self) :
       
   157         """
       
   158             Comparisons between Nodes
       
   159         """
       
   160 
       
   161         return cmp((self.parent, self.name), (other.parent, other.name))
       
   162 
       
   163 class File (Node) :
       
   164     """
       
   165         A file. Simple, eh?
       
   166     """
       
   167 
       
   168     @property
       
   169     def basename (self) :
       
   170         """
       
   171             Returns the "base" part of this file's name, i.e. the filename without the extension
       
   172         """
       
   173 
       
   174         basename, _ = os.path.splitext(self.name)
       
   175 
       
   176         return basename
       
   177     
       
   178     @property
       
   179     def fileext (self) :
       
   180         """
       
   181             Returns the file extension part of the file's name, without any leading dot
       
   182         """
       
   183 
       
   184         _, fileext = os.path.splitext(self.name)
       
   185 
       
   186         return fileext.rstrip('.')
       
   187 
       
   188     def matchext (self, ext_list) :
       
   189         """
       
   190             Tests if this file's extension is part of the recognized list of extensions
       
   191         """
       
   192         
       
   193         return (self.fileext.lower() in ext_list)
       
   194 
       
   195 class Directory (Node) :
       
   196     """
       
   197         A directory is a node that contains other nodes.
       
   198     """
       
   199 
       
   200     # a list of (test_func, node_type) tuples for use by children() to build subnodes with
       
   201     NODE_TYPES = None
       
   202    
       
   203     def subdir (self, name) :
       
   204         """
       
   205             Returns a Directory object representing the name underneath this dir
       
   206         """
       
   207 
       
   208         return Directory(self, name=name)
       
   209     
       
   210     def subfile (self, name) :
       
   211         """
       
   212             Returns a File object representing the name underneath this dir
       
   213         """
       
   214 
       
   215         return Directory(self, name=name)
       
   216 
       
   217     def listdir (self, skip_dotfiles=True) :
       
   218         """
       
   219             Yield a series of raw fsnames for nodes in this dir
       
   220         """
       
   221         
       
   222         # expressed 
       
   223         return (fsname for fsname in os.listdir(self.path) if not (skip_dotfiles and fsname.startswith('.')))
       
   224 
       
   225     def child_nodes (self, skip_dotfiles=True) :
       
   226         """
       
   227             Yield a series of nodes contained in this dir
       
   228         """
       
   229 
       
   230         return (Node(self, fsname) for fsname in self.listdir(skip_dotfiles))
       
   231 
       
   232     def __iter__ (self) :
       
   233         """
       
   234             Iterating over a Directory yields sub-Nodes.
       
   235 
       
   236             Dotfiles are skipped.
       
   237         """
       
   238         
       
   239         return self.childnodes()
       
   240 
       
   241     @property
       
   242     def root_path (self) :
       
   243         """
       
   244             Build and return a relative path to the root of this dir tree
       
   245         """
       
   246         
       
   247         # build using parent root_path
       
   248         return os.path.join('..', self.parent.root_path)
       
   249  
       
   250     def children (self) :
       
   251         """
       
   252             Yield a series of Node subclasses representing the items in this dir.
       
   253             
       
   254             This uses self.NODE_TYPES to figure out what kind of sub-node object to build. This should be a list of
       
   255                 (test_func, node_type)
       
   256 
       
   257             tuples, of which the first is a function that takes a Node as it's sole argument, and returns a boolean.
       
   258             For the first test_func which returns True, a Node-subclass object is constructed using node_type.from_node.
       
   259         """
       
   260 
       
   261         for node in self :
       
   262             # figure out what type to use
       
   263             for test_func, node_type in self.NODE_TYPES :
       
   264                 if test_func(node) :
       
   265                     # matches, build
       
   266                     yield node_type.from_node(node)
       
   267 
       
   268             else :
       
   269                 # unknown file type!
       
   270                 raise Exception("unrecongized type of file: %s" % node);
       
   271 
       
   272 # assign default Directory.NODE_TYPES
       
   273 Directory.NODE_TYPES = [
       
   274     (Node.is_dir,   Directory),
       
   275     (Node.is_file,  File),
       
   276 ]
       
   277 
       
   278 
       
   279 class Root (Directory) :
       
   280     """
       
   281         A special Directory that overrides the Node methods to anchor the recursion/etc at some 'real' filesystem path.
       
   282     """
       
   283 
       
   284     def __init__ (self, fspath, config) :
       
   285         """
       
   286             Construct the directory tree root at the given 'real' path, which must be a raw str
       
   287         """
       
   288 
       
   289         # abuse Node's concept of a "name" a bit
       
   290         super(Root, self).__init__(None, fspath, config=config)
       
   291 
       
   292     @property
       
   293     def path (self) :
       
   294         """
       
   295             Returns the raw path
       
   296         """
       
   297 
       
   298         return self.fsname
       
   299 
       
   300     @property
       
   301     def unicodepath (self) :
       
   302         """
       
   303             Returns the raw decoded path
       
   304         """
       
   305 
       
   306         return self.name
       
   307 
       
   308     @property
       
   309     def root_path (self) :
       
   310         """
       
   311             Returns an empty string representing this dir
       
   312         """
       
   313 
       
   314         return ''
       
   315 
       
   316     def __repr__ (self) :
       
   317         """
       
   318             Override Node.__repr__ to not use self.parent.path
       
   319         """
       
   320 
       
   321         return "Root(%r)" % self.fsname
       
   322