diff options
| author | Steven Knight <knight@baldmt.com> | 2001-09-19 12:16:13 (GMT) |
|---|---|---|
| committer | Steven Knight <knight@baldmt.com> | 2001-09-19 12:16:13 (GMT) |
| commit | 7cd353fbcb81398e12a5a6b564eb4288449b4bc6 (patch) | |
| tree | 0fe333cf1e2379c653b120e0fc416b3c97b66838 /src/engine/SCons/Node/FS.py | |
| parent | 21d1baa81a1e83002c197c8a49a81417856ce15d (diff) | |
| download | SCons-7cd353fbcb81398e12a5a6b564eb4288449b4bc6.zip SCons-7cd353fbcb81398e12a5a6b564eb4288449b4bc6.tar.gz SCons-7cd353fbcb81398e12a5a6b564eb4288449b4bc6.tar.bz2 | |
Clean up the Node.FS class.
Diffstat (limited to 'src/engine/SCons/Node/FS.py')
| -rw-r--r-- | src/engine/SCons/Node/FS.py | 283 |
1 files changed, 203 insertions, 80 deletions
diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py index 2d1c8ea..9cd0607 100644 --- a/src/engine/SCons/Node/FS.py +++ b/src/engine/SCons/Node/FS.py @@ -1,89 +1,188 @@ -"""SCons.Node.FS +"""scons.Node.FS File system nodes. +This initializes a "default_fs" Node with an FS at the current directory +for its own purposes, and for use by scripts or modules looking for the +canonical default. + """ __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" - - import os import os.path -import SCons.Node +from SCons.Node import Node +from UserDict import UserDict +import sys + +class PathName: + """This is a string like object with limited capabilities (i.e., + cannot always be used interchangeably with strings). This class + is used by PathDict to manage case-insensitive path names. It preserves + the case of the string with which it was created, but on OS's with + case insensitive paths, it will hash equal to any case of the same + path when placed in a dictionary.""" + try: + convert_path = unicode + except NameError: + convert_path = str + def __init__(self, path_name=''): + self.data = PathName.convert_path(path_name) + self.norm_path = os.path.normcase(self.data) -Top = None -Root = {} + def __hash__(self): + return hash(self.norm_path) + def __cmp__(self, other): + return cmp(self.norm_path, + os.path.normcase(PathName.convert_path(other))) + def __rcmp__(self, other): + return cmp(os.path.normcase(PathName.convert_path(other)), + self.norm_path) + def __str__(self): + return str(self.data) + def __repr__(self): + return repr(self.data) +class PathDict(UserDict): + """This is a dictionary-like class meant to hold items keyed + by path name. The difference between this class and a normal + dictionary is that string or unicode keys will act differently + on OS's that have case-insensitive path names. Specifically + string or unicode keys of different case will be treated as + equal on the OS's. + All keys are implicitly converted to PathName objects before + insertion into the dictionary.""" -def init(path = None): - """Initialize the Node.FS subsystem. + def __init__(self, initdict = {}): + UserDict.__init__(self, initdict) + old_dict = self.data + self.data = {} + for key, val in old_dict.items(): + self.data[PathName(key)] = val - The supplied path is the top of the source tree, where we - expect to find the top-level build file. If no path is - supplied, the current directory is the default. - """ - global Top - if path == None: - path = os.getcwd() - Top = lookup(Dir, path, directory = None) - Top.path = '.' - -def lookup(fsclass, name, directory = Top): - """Look up a file system node for a path name. If the path - name is relative, it will be looked up relative to the - specified directory node, or to the top-level directory - if no node was specified. An initial '#' specifies that - the name will be looked up relative to the top-level directory, - regardless of the specified directory argument. Returns the - existing or newly-created node for the specified path name. - The node returned will be of the specified fsclass (Dir or - File). - """ - global Top - head, tail = os.path.split(name) - if not tail: - drive, path = os.path.splitdrive(head) - if not Root.has_key(drive): - Root[drive] = Dir(head, None) - Root[drive].abspath = head - Root[drive].path = head - return Root[drive] - if tail[0] == '#': - directory = Top - tail = tail[1:] - elif directory is None: - directory = Top - if head: - directory = lookup(Dir, head, directory) - try: - self = directory.entries[tail] - except AttributeError: - # There was no "entries" attribute on the directory, - # which essentially implies that it was a file. - # Return it as a more descriptive exception. - raise TypeError, directory - except KeyError: - # There was to entry for "tail," so create the new - # node and link it in to the existing structure. - self = fsclass(tail, directory) - self.name = tail - if self.path[0:2] == "./": - self.path = self.path[2:] - directory.entries[tail] = self - except: - raise - if self.__class__.__name__ != fsclass.__name__: - # Here, we found an existing node for this path, - # but it was the wrong type (a File when we were - # looking for a Dir, or vice versa). - raise TypeError, self - return self + def __setitem__(self, key, val): + self.data[PathName(key)] = val + + def __getitem__(self, key): + return self.data[PathName(key)] + + def __delitem__(self, key): + del(self.data[PathName(key)]) + + if not hasattr(UserDict, 'setdefault'): + def setdefault(self, key, value): + try: + return self.data[PathName(key)] + except KeyError: + self.data[PathName(key)] = value + return value + +class FS: + def __init__(self, path = None): + """Initialize the Node.FS subsystem. + + The supplied path is the top of the source tree, where we + expect to find the top-level build file. If no path is + supplied, the current directory is the default. + + The path argument must be a valid absolute path. + """ + if path == None: + path = os.getcwd() + self.Root = PathDict() + self.Top = self.__doLookup(Dir, path) + self.Top.path = '.' + + def __doLookup(self, fsclass, name, directory=None): + """This method differs from the File and Dir factory methods in + one important way: the meaning of the directory parameter. + In this method, if directory is None or not supplied, the supplied + name is expected to be an absolute path. If you try to look up a + relative path with directory=None, then an AssertionError will be + raised.""" + head, tail = os.path.split(os.path.normpath(name)) + if not tail: + # We have reached something that looks like a root + # of an absolute path. What we do here is a little + # weird. If we are on a UNIX system, everything is + # well and good, just return the root node. + # + # On DOS/Win32 things are strange, since a path + # starting with a slash is not technically an + # absolute path, but a path relative to the + # current drive. Therefore if we get a path like + # that, we will return the root Node of the + # directory parameter. If the directory parameter is + # None, raise an exception. + drive, tail = os.path.splitdrive(head) + if sys.platform is 'win32' and not drive: + if not directory: + raise OSError, 'No drive letter supplied for absolute path.' + return directory.root() + return self.Root.setdefault(drive, Dir(tail)) + if head: + # Recursively look up our parent directories. + directory = self.__doLookup(Dir, head, directory) + else: + # This path looks like a relative path. No leading slash or drive + # letter. Therefore, we will look up this path relative to the + # supplied top-level directory. + assert directory, "Tried to lookup a node by relative path with no top-level directory supplied." + ret = directory.entries.setdefault(tail, fsclass(tail, directory)) + if not isinstance(ret, fsclass): + raise TypeError, ret + return ret + + def __transformPath(self, name, directory): + """Take care of setting up the correct top-level directory, + usually in preparation for a call to doLookup(). + + If the path name is prepended with a '#', then it is unconditionally + interpreted as replative to the top-level directory of this FS. + + If directory is None, and name is a relative path, + then the same applies. + """ + if name[0] == '#': + directory = self.Top + name = os.path.join(os.path.normpath('./'), name[1:]) + elif not directory: + directory = self.Top + return (name, directory) + + def File(self, name, directory = None): + """Lookup or create a File node with the specified name. If + the name is a relative path (begins with ./, ../, or a file name), + then it is looked up relative to the supplied directory node, + or to the top level directory of the FS (supplied at construction + time) if no directory is supplied. + + This method will raise TypeError if a directory is found at the + specified path. + """ + name, directory = self.__transformPath(name, directory) + return self.__doLookup(File, name, directory) + + def Dir(self, name, directory = None): + """Lookup or create a Dir node with the specified name. If + the name is a relative path (begins with ./, ../, or a file name), + then it is looked up relative to the supplied directory node, + or to the top level directory of the FS (supplied at construction + time) if no directory is supplied. + + This method will raise TypeError if a normal file is found at the + specified path. + """ + name, directory = self.__transformPath(name, directory) + return self.__doLookup(Dir, name, directory) + + # XXX TODO? # Annotate with the creator @@ -94,20 +193,33 @@ def lookup(fsclass, name, directory = Top): # linked_targets # is_accessible -class Dir(SCons.Node.Node): +class Dir(Node): """A class for directories in a file system. """ - def __init__(self, name, directory): - self.entries = {} - self.entries['.'] = self - self.entries['..'] = directory - if not directory is None: - self.abspath = os.path.join(directory.abspath, name, '') - self.path = os.path.join(directory.path, name, '') + def __init__(self, name, directory = None): + self.entries = PathDict() + self.entries['.'] = self + + if directory: + self.entries['..'] = directory + self.abspath = os.path.join(directory.abspath, name, '') + if str(directory.path) == '.': + self.path = os.path.join(name, '') + else: + self.path = os.path.join(directory.path, name, '') + else: + self.abspath = self.path = name + self.entries['..'] = None def up(self): - return self.entries['..'] + return self.entries['..'] + + def root(self): + if not self.entries['..']: + return self + else: + return self.entries['..'].root() # XXX TODO? @@ -130,10 +242,21 @@ class Dir(SCons.Node.Node): # is_under # relpath -class File(SCons.Node.Node): +class File(Node): """A class for files in a file system. """ def __init__(self, name, directory): - self.abspath = os.path.join(directory.abspath, name) - self.path = os.path.join(directory.path, name) + self.abspath = os.path.join(directory.abspath, name) + if str(directory.path) == '.': + self.path = name + else: + self.path = os.path.join(directory.path, name) + self.parent = directory + + def root(self): + return self.parent.root() + + + +default_fs = FS() |
