Differences between revisions 4 and 5
Revision 4 as of 2008-12-17 01:46:42
Size: 48
Editor: PS-US-CA-01
Comment: FIELD_OTHER
Revision 5 as of 2008-12-17 21:18:52
Size: 38281
Editor: PaulBoddie
Comment:
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
FIELD_MESSAGE_nocnacnar
----
CategoryEditors
#format PYTHON
# -*- coding: iso-8859-1 -*-
""" path.py - An object representing a path to a file or a directory.

Based on the path module by Jason Orendorff
(http://www.jorendorff.com/articles/python/path)

Written by Noam Raphael to show the idea of using a tuple instead of
a string, and to reduce the number of methods.

Currently only implements posix and nt paths - more can be added.

"""

import os
import stat
import itertools
import fnmatch
import re
import string

class StatWrapper(object):
    """ A wrapper around stat_result objects which gives additional properties.
    
    This object is a wrapper around a stat_result object. It allows access
    to all the original object's attributes, and adds a few convinient
    properties, by using the stat module.
    
    This object should have been a subclass posix.stat_result - it simply
    isn't possible currently. This functionality may also be integrated into
    the original type.
    """
    
    __slots__ = ['_stat']
    
    def __init__(self, stat):
        self._stat = stat
        
    def __getattribute__(self, attr, *default):
        try:
            return object.__getattribute__(self, attr, *default)
        except AttributeError:
            return getattr(self._stat, attr, *default)

    # Mode properties
    
    @property
    def isdir(self):
        return stat.S_ISDIR(self.st_mode)
    @property
    def isfile(self):
        return stat.S_ISREG(self.st_mode)
    @property
    def islink(self):
        return stat.S_ISLNK(self.st_mode)
    
    # Easier names properties

    @property
    def size(self):
        return self.st_size
    @property
    def mtime(self):
        return self.st_mtime
    @property
    def atime(self):
        return self.st_atime
    @property
    def ctime(self):
        return self.st_ctime


class BasePath(tuple):
    """ The base, abstract, path type.
    
    The OS-specific path types inherit from it.
    """

    # ----------------------------------------------------------------
    # We start with methods which don't use system calls - they just
    # manipulate paths.

    class _BaseRoot(object):
        """ Represents a start location for a path.
        
        A Root is an object which may be the first element of a path tuple,
        and represents from where to start the path.
        
        On posix, there's only one: ROOT (singleton).
        On nt, there are a few:
          CURROOT - the root of the current drive (singleton)
          Drive(letter) - the root of a specific drive
          UnrootedDrive(letter) - the current working directory on a specific
                                  drive
          UNCRoot(host, mountpoint) - a UNC mount point

        The class for each OS has its own root classes, which should inherit
        from _OSBaseRoot.

        str(root) should return the string name of the root. The string should
        identify the root: two root elements with the same string should have
        the same meaning. To allow meaningful sorting of path objects, root
        objects can be compared to strings and other root objects. They are
        smaller than all strings, and are compared with other root objects
        according to their string name.

        Every Root object should contain the "isabs" attribute, which is True
        if changes in the current working directory won't change the meaning
        of the root and False otherwise. (CURROOT and UnrootedDrive aren't
        absolute)
        If isabs is True, it should also implement the abspath() method, which
        should return an absolute path object, equivalent to the root when the
        call was made.
        """
        isabs = None

        def abspath(self):
            if self.abspath:
                raise NotImplementedError, 'This root is already absolute'
            else:
                raise NotImplementedError, 'abspath is abstract'

        def __str__(self):
            raise NotImplementedError, '__str__ is abstract'

        def __cmp__(self, other):
            if isinstance(other, str):
                return -1
            elif isinstance(other, BasePath._BaseRoot):
                return cmp(str(self), str(other))
            else:
                raise TypeError, 'Comparison not defined'

        def __hash__(self):
            # This allows path objects to be hashable
            return hash(str(self))

    # _OSBaseRoot should be the base of the OS-specific root classes, which
    # should inherit from _BaseRoot
    _OSBaseRoot = None

    # These string constants should be filled by subclasses - they are real
    # directory names
    curdir = None
    pardir = None

    # These string constants are used by default implementations of methods,
    # but are not part of the interface - the whole idea is for the interface
    # to hide those details.
    _sep = None
    _altsep = None

    @staticmethod
    def _parse_str(pathstr):
        # Concrete path classes should implement _parse_str to get a path
        # string and return an iterable over path elements.
        raise NotImplementedError, '_parse_str is abstract'

    @staticmethod
    def normcasestr(string):
        """ Normalize the case of one path element.
        
        This default implementation returns string unchanged. On
        case-insensitive platforms, it returns the normalized string.
        """
        return string

    # We make this method a property, to show that it doesn't use any
    # system calls.
    # Case-sensitive subclasses can redefine it to return self.
    @property
    def normcase(self):
        """ Return an equivalent path with case-normalized elements. """
        if self.isrel:
            return self.__class__(self.normcasestr(element)
                                  for element in self)
        else:
            def gen():
                it = iter(self)
                yield it.next()
                for element in it:
                    yield self.normcasestr(element)
            return self.__class__(gen())

    @classmethod
    def _normalize_elements(cls, elements):
        # This method gets an iterable over path elements.
        # It should return an iterator over normalized path elements -
        # that is, curdir elements should be ignored.
        
        for i, element in enumerate(elements):
            if isinstance(element, str):
                if element != cls.curdir:
                    if (not element or
                        cls._sep in element or
                        (cls._altsep and cls._altsep in element)):
                        # Those elements will cause path(str(x)) != x
                        raise ValueError, "Element %r is invalid" % element
                    yield element
            elif i == 0 and isinstance(element, cls._OSBaseRoot):
                yield element
            else:
                raise TypeError, "Element %r is of a wrong type" % element

    def __new__(cls, arg=None):
        """ Create a new path object.
        
        If arg isn't given, an empty path, which represents the current
        working directory, is returned.
        If arg is a string, it is parsed into a logical path.
        If arg is an iterable over path elements, a new path is created from
        them.
        """
        if arg is None:
            return tuple.__new__(cls)
        elif type(arg) is cls:
            return arg
        elif isinstance(arg, str):
            return tuple.__new__(cls, cls._parse_str(arg))
        else:
            return tuple.__new__(cls, cls._normalize_elements(arg))

    def __init__(self, arg=None):
        # Since paths are immutable, we can cache the string representation
        self._cached_str = None

    def _build_str(self):
        # Return a string representation of self.
        #
        # This is a default implementation, which may be overriden by
        # subclasses (form example, MacPath)
        if not self:
            return self.curdir
        elif isinstance(self[0], self._OSBaseRoot):
            return str(self[0]) + self._sep.join(self[1:])
        else:
            return self._sep.join(self)

    def __str__(self):
        """ Return a string representation of self. """
        if self._cached_str is None:
            self._cached_str = self._build_str()
        return self._cached_str

    def __repr__(self):
        # We want path, not the real class name.
        return 'path(%r)' % str(self)

    @property
    def isabs(self):
        """ Return whether this path represent an absolute path.

        An absolute path is a path whose meaning doesn't change when the
        the current working directory changes.
        
        (Note that this is not the same as "not self.isrelative")
        """
        return len(self) > 0 and \
               isinstance(self[0], self._OSBaseRoot) and \
               self[0].isabs

    @property
    def isrel(self):
        """ Return whether this path represents a relative path.

        A relative path is a path without a root element, so it can be
        concatenated to other paths.

        (Note that this is not the same as "not self.isabs")
        """
        return len(self) == 0 or \
               not isinstance(self[0], self._OSBaseRoot)

    # Wrap a few tuple methods to return path objects

    def __add__(self, other):
        other = self.__class__(other)
        if not other.isrel:
            raise ValueError, "Right operand should be a relative path"
        return self.__class__(itertools.chain(self, other))

    def __radd__(self, other):
        if not self.isrel:
            raise ValueError, "Right operand should be a relative path"
        other = self.__class__(other)
        return self.__class__(itertools.chain(other, self))

    def __getslice__(self, *args):
        return self.__class__(tuple.__getslice__(self, *args))

    def __mul__(self, *args):
        if not self.isrel:
            raise ValueError, "Only relative paths can be multiplied"
        return self.__class__(tuple.__mul__(self, *args))

    def __rmul__(self, *args):
        if not self.isrel:
            raise ValueError, "Only relative paths can be multiplied"
        return self.__class__(tuple.__rmul__(self, *args))

    def __eq__(self, other):
        return tuple.__eq__(self, self.__class__(other))
    def __ge__(self, other):
        return tuple.__ge__(self, self.__class__(other))
    def __gt__(self, other):
        return tuple.__gt__(self, self.__class__(other))
    def __le__(self, other):
        return tuple.__le__(self, self.__class__(other))
    def __lt__(self, other):
        return tuple.__lt__(self, self.__class__(other))
    def __ne__(self, other):
        return tuple.__ne__(self, self.__class__(other))
        

    # ----------------------------------------------------------------
    # Now come the methods which use system calls.

    # --- Path transformation which use system calls

    @classmethod
    def cwd(cls):
        return cls(os.getcwd())

    def chdir(self):
        return os.chdir(str(self))

    def abspath(self):
        if not self:
            return self.cwd()
        if isinstance(self[0], self._OSBaseRoot):
            if self[0].isabs:
                return self
            else:
                return self[0].abspath() + self[1:]
        else:
            return self.cwd() + self

    def realpath(self):
        return self.__class__(os.path.realpath(str(self)))

    def relpathto(self, dst):
        """ Return a relative path from self to dest.

        This method examines self.realpath() and dest.realpath(). If
        they have the same root element, a path in the form
        path([path.pardir, path.pardir, ..., dir1, dir2, ...])
        is returned. If they have different root elements,
        dest.realpath() is returned.
        """
        src = self.realpath()
        dst = self.__class__(dst).realpath()

        if src[0] == dst[0]:
            # They have the same root
            
            # find the length of the equal prefix
            i = 1
            while i < len(src) and i < len(dst) and \
                  self.normcasestr(src[i]) == self.normcasestr(dst[i]):
                i += 1

            return [self.pardir] * (len(src) - i) + dst[i:]

        else:
            # They don't have the same root
            return dst
            

    

    # --- Expand

    def expanduser(self):
        return path(os.path.expanduser(str(self)))

    def expandvars(self):
        return path(os.path.expandvars(str(self)))
    

    # --- Info about the path

    def stat(self):
        return StatWrapper(os.stat(str(self)))
    
    def exists(self):
        try:
            self.stat()
        except OSError:
            return False
        else:
            return True

    def isdir(self):
        try:
            return self.stat().isdir
        except OSError:
            return False

    def isfile(self):
        try:
            return self.stat().isfile
        except OSError:
            return False
        
    def lstat(self):
        return StatWrapper(os.lstat(str(self)))

    def lexists(self):
        try:
            self.lstat()
        except OSError:
            return False
        else:
            return True

    def lisdir(self):
        try:
            return self.stat().lisdir
        except OSError:
            return False

    def lisfile(self):
        try:
            return self.stat().lisfile
        except OSError:
            return False

    def islink(self):
        try:
            return self.lstat().islink
        except OSError:
            return False
        
    def ismount(self):
        return os.path.ismount(str(self))

    def access(self, mode):
        """ Return true if current user has access to this path.

        mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
        """
        return os.access(str(self), mode)

    # Additional methods in subclasses:
    # statvfs (PosixPath)
    # pathconf (PosixPath, XXX MacPath)
    # samefile (PosixPath, XXX MacPath)


    # --- Modifying operations on files and directories

    def utime(self, times):
        """ Set the access and modified times of this file. """
        os.utime(str(self), times)

    def chmod(self, mode):
        os.chmod(str(self), mode)

    def rename(self, new):
        os.rename(str(self), str(new))

    # Additional methods in subclasses:
    # chown (PosixPath, XXX MacPath)
    # lchown (PosixPath, XXX MacPath)


    # --- Create/delete operations on directories

    def mkdir(self, mode=0777):
        os.mkdir(str(self), mode)

    def makedirs(self, mode=0777):
        os.makedirs(str(self), mode)

    def rmdir(self):
        os.rmdir(str(self))

    def removedirs(self, base=None):
        """ Remove empty directories recursively.
        
        If the directory is empty, remove it. If the parent directory becomes
        empty, remove it too. Continue until a directory can't be removed,
        because it's not empty or for other reasons.
        If base is given, it should be a prefix of self. base won't be removed
        even if it becomes empty.
        Note: only directories explicitly listed in the path will be removed.
        This means that if self is a relative path, predecesors of the
        current working directory won't be removed.
        """
        if not self.stat().isdir:
            raise OSError, 'removedirs only works on directories.'
        base = self.__class__(base)
        if base:
            if not self[:len(base)] == base:
                raise ValueError, 'base should be a prefix of self.'
            stopat = len(base)
        else:
            stopat = 0
        for i in xrange(len(self), stopat, -1):
            try:
                self[:i].rmdir()
            except OSError:
                break

    def rmtree(self, *args):
        return shutil.rmtree(str(self), *args)


    # --- Modifying operations on files

    def touch(self):
        """ Set the access/modified times of this file to the current time.
        Create the file if it does not exist.
        """
        fd = os.open(str(self), os.O_WRONLY | os.O_CREAT, 0666)
        os.close(fd)
        os.utime(str(self), None)

    def remove(self):
        os.remove(str(self))

    def copy(self, dst, copystat=False):
        """ Copy file from self to dst.

        If copystat is False, copy data and mode bits ("cp self dst").
        If copystat is True, copy data and all stat info ("cp -p self dst").

        The destination may be a directory. If so, a file with the same base
        name as self will be created in that directory.
        """
        dst = self.__class__(dst)
        if dst.stat().isdir:
            dst += self[-1]
        shutil.copyfile(str(self), str(dst))
        if copystat:
            shutil.copystat(str(self), str(dst))
        else:
            shutil.copymode(str(self), str(dst))

    def move(self, dst):
        dst = self.__class__(dst)
        return shutil.move(str(self), str(dst))
        

    # --- Links

    # In subclasses:
    # link (PosixPath, XXX MacPath)
    # writelink (PosixPath) - what about MacPath?
    # readlink (PosixPath, XXX MacPath)
    # readlinkpath (PosixPath, XXXMacPath)


    # --- Extra

    # In subclasses:
    # mkfifo (PosixPath, XXX MacPath)
    # mknod (PosixPath, XXX MacPath)
    # chroot (PosixPath, XXX MacPath)
    #
    # startfile (NTPath)


    # --- Globbing

    # If the OS supports it, _id should be a function that gets a stat object
    # and returns a unique id of a file.
    # It the OS doesn't support it, it should be None.
    _id = None

    @staticmethod
    def _match_element(comp_element, element):
        # Does a filename match a compiled pattern element?
        # The filename should be normcased.
        if comp_element is None:
            return True
        elif isinstance(comp_element, str):
            return comp_element == element
        else:
            return comp_element.match(element)

    def _glob(cls, pth, comp_pattern, topdown, onlydirs, onlyfiles,
              positions, on_path, stat):
        """ The recursive function used by glob.

        This version treats symbolic links as files. Broken symlinks won't be
        listed.

        pth is a dir in which we search.
        
        comp_pattern is the compiled pattern. It's a sequence which should
        consist of three kinds of elements:
        * None - matches any number of subdirectories, including 0.
        * a string - a normalized name, when exactly one name can be matched.
        * a regexp - for testing if normalized names match.

        positions is a sequence of positions on comp_pattern that children of
        path may match. On the first call, if will be [0].

        on_path is a set of inode identifiers on path, or None if circles
        shouldn't be checked.

        stat is the appropriate stat function - cls.stat or cls.lstat.
        """

        if len(positions) == 1 and isinstance(comp_pattern[positions[0]], str):
            # We don't have to listdir if exactly one file name can match.
            # Since we always stat the files, it's ok if the file doesn't exist.
            listdir = [comp_pattern[positions[0]]]
        else:
            listdir = os.listdir(str(pth))
            listdir.sort()

        for subfile in listdir:
            newpth = pth + subfile
            # We don't strictly need to stat a file if we don't follow symlinks
            # AND positions == [len(comp_pattern)-1] AND
            # not isinstance(comp_pattern[-1], str), but do me a favour...
            try:
                st = stat(newpth)
            except OSError:
                continue
            newpositions = []
            subfilenorm = cls.normcasestr(subfile)
            
            if topdown:
                # If not topdown, it happens after we handle subdirs
                if positions[-1] == len(comp_pattern) - 1:
                    if cls._match_element(comp_pattern[-1], subfilenorm):
                        if not ((onlydirs and not st.isdir) or
                                (onlyfiles and not st.isfile)):
                            yield newpth

            for pos in reversed(positions):
                if st.isdir:
                    comp_element = comp_pattern[pos]
                    if pos + 1 < len(comp_pattern):
                        if cls._match_element(comp_element, subfilenorm):
                            newpositions.append(pos + 1)
                            if comp_pattern[pos + 1] is None:
                                # We should stop at '..'
                                break
                    if comp_element is None:
                        newpositions.append(pos)
                        # We don't have to break - there are not supposed
                        # to be any positions before '..'.

            if newpositions:
                newpositions.reverse()

                if on_path is not None:
                    newpath_id = cls._id(st)
                    if newpath_id in on_path:
                        raise OSError, "Circular path encountered"
                    on_path.add(newpath_id)

                for x in cls._glob(newpth,
                                   comp_pattern, topdown, onlydirs, onlyfiles,
                                   newpositions, on_path, stat):
                    yield x

                if on_path is not None:
                    on_path.remove(newpath_id)

            if not topdown:
                # If topdown, it happens after we handle subdirs
                if positions[-1] == len(comp_pattern) - 1:
                    if cls._match_element(comp_pattern[-1], subfilenorm):
                        if not ((onlydirs and not st.isdir) or
                                (onlyfiles and not st.isfile)):
                            yield newpth

    _magic_check = re.compile('[*?[]')

    @classmethod
    def _has_magic(cls, s):
        return cls._magic_check.search(s) is not None

    _cache = {}

    @classmethod
    def _compile_pattern(cls, pattern):
        # Get a pattern, return the list of compiled pattern elements
        # and the list of initial positions.
        pattern = cls(pattern)
        if not pattern.isrel:
            raise ValueError, "pattern should be a relative path."

        comp_pattern = []
        last_was_none = False
        for element in pattern:
            element = cls.normcasestr(element)
            if element == '**':
                if not last_was_none:
                    comp_pattern.append(None)
            else:
                last_was_none = False
                if not cls._has_magic(element):
                    comp_pattern.append(element)
                else:
                    try:
                        r = cls._cache[element]
                    except KeyError:
                        r = re.compile(fnmatch.translate(element))
                        cls._cache[element] = r
                    comp_pattern.append(r)

        if comp_pattern[0] is None and len(comp_pattern) > 1:
            positions = [0, 1]
        else:
            positions = [0]

        return comp_pattern, positions

    def match(self, pattern):
        """ Return whether self matches the given pattern.

        pattern has the same meaning as in the glob method.
        self should be relative.

        This method doesn't use any system calls.
        """
        if not self.isrel:
            raise ValueError, "self must be a relative path"
        comp_pattern, positions = self._compile_pattern(pattern)

        for element in self.normcase:
            newpositions = []
            for pos in reversed(positions):
                if pos == len(comp_pattern):
                    # We matched the pattern but the path isn't finished -
                    # too bad
                    continue
                comp_element = comp_pattern[pos]
                if self._match_element(comp_element, element):
                    newpositions.append(pos + 1)
                if comp_element is None:
                    newpositions.append(pos)
                    # No need to continue after a '**'
                    break
            newpositions.reverse()
            positions = newpositions
            if not positions:
                # No point in carrying on
                break

        return (len(comp_pattern) in positions)

    def glob(self, pattern='*', topdown=True, onlydirs=False, onlyfiles=False):
        """ Return an iterator over all files in self matching pattern.

        pattern should be a relative path, which may include wildcards.
        In addition to the regular shell wildcards, you can use '**', which
        matches any number of directories, including 0.

        If topdown is True (the default), a directory is yielded before its
        descendents. If it's False, a directory is yielded after its
        descendents.

        If onlydirs is True, only directories will be yielded. If onlyfiles
        is True, only regular files will be yielded.

        This method treats symbolic links as regular files. Broken symlinks
        won't be yielded.
        """

        if onlydirs and onlyfiles:
            raise ValueError, \
                  "Only one of onlydirs and onlyfiles can be specified."

        comp_pattern, positions = self._compile_pattern(pattern)

        if self._id is not None and None in comp_pattern:
            on_path = set([self._id(self.stat())])
        else:
            on_path = None
            
        for x in self._glob(self, comp_pattern, topdown, onlydirs, onlyfiles,
                            positions, on_path, self.__class__.stat):
            yield x
        
    def lglob(self, pattern='*', topdown=True, onlydirs=False, onlyfiles=False):
        """ Return an iterator over all files in self matching pattern.

        pattern should be a relative path, which may include wildcards.
        In addition to the regular shell wildcards, you can use '**', which
        matches any number of directories, including 0.

        If topdown is True (the default), a directory is yielded before its
        descendents. If it's False, a directory is yielded after its
        descendents.

        If onlydirs is True, only directories will be yielded. If onlyfiles
        is True, only regular files will be yielded.

        This method treats symbolic links as special files - they won't be
        followed, and they will be yielded even if they're broken.
        """

        if onlydirs and onlyfiles:
            raise ValueError, \
                  "Only one of onlydirs and onlyfiles can be specified."

        comp_pattern, positions = self._compile_pattern(pattern)
            
        for x in self._glob(self, comp_pattern, topdown, onlydirs, onlyfiles,
                            positions, None, self.__class__.lstat):
            yield x


class PosixPath(BasePath):
    """ Represents POSIX paths. """
    
    class _PosixRoot(BasePath._BaseRoot):
        """ Represents the filesystem root (/).
        
        There's only one root on posix systems, so this is a singleton.
        """
        instance = None
        def __new__(cls):
            if cls.instance is None:
                instance = object.__new__(cls)
                cls.instance = instance
            return cls.instance
        
        def __str__(self):
            return '/'

        def __repr__(self):
            return 'path.ROOT'

        isabs = True

    _OSBaseRoot = _PosixRoot

    ROOT = _PosixRoot()

    # Public constants
    curdir = '.'
    pardir = '..'

    # Private constants
    _sep = '/'
    _altsep = None

    @classmethod
    def _parse_str(cls, pathstr):
        # get a path string and return an iterable over path elements.
        if pathstr.startswith('/'):
            if pathstr.startswith('//') and not pathstr.startswith('///'):
                # Two initial slashes have application-specific meaning
                # in POSIX, and it's not supported currently.
                raise NotImplementedError, \
                      "Paths with two leading slashes aren't supported."
            yield cls.ROOT
        for element in pathstr.split('/'):
            if element == '' or element == cls.curdir:
                continue
            # '..' aren't specially treated, since popping the last
            # element isn't correct if the last element was a symbolic
            # link.
            yield element


    # POSIX-specific methods
    

    # --- Info about the path

    def statvfs(self):
        """ Perform a statvfs() system call on this path. """
        return os.statvfs(str(self))

    def pathconf(self, name):
        return os.pathconf(str(self), name)

    def samefile(self, other):
        other = self.__class__(other)
        s1 = self.stat()
        s2 = other.stat()
        return s1.st_ino == s2.st_ino and \
               s1.st_dev == s2.st_dev


    # --- Modifying operations on files and directories

    def chown(self, uid=None, gid=None):
        if uid is None:
            uid = -1
        if gid is None:
            gid = -1
        return os.chown(str(self), uid, gid)
    
    def lchown(self, uid=None, gid=None):
        if uid is None:
            uid = -1
        if gid is None:
            gid = -1
        return os.lchown(str(self), uid, gid)


    # --- Links

    def link(self, newpath):
        """ Create a hard link at 'newpath', pointing to this file. """
        os.link(str(self), str(newpath))

    def writelink(self, src):
        """ Create a symbolic link at self, pointing to src.

        src may be any string. Note that if it's a relative path, it
        will be interpreted relative to self, not relative to the current
        working directory.
        """
        os.symlink(str(src), str(self))

    def readlink(self):
        """ Return the path to which this symbolic link points.

        The result is a string, which may be an absolute path, a
        relative path (which should be interpreted relative to self[:-1]),
        or any arbitrary string.
        """
        return os.readlink(str(self))

    def readlinkpath(self):
        """ Return the path to which this symbolic link points. """
        linkpath = self.__class__(self.readlink())
        if linkpath.isrel:
            return self + linkpath
        else:
            return linkpath


    # --- Extra

    def mkfifo(self, *args):
        return os.mkfifo(str(self), *args)

    def mknod(self, *args):
        return os.mknod(str(self), *args)

    def chroot(self):
        return os.chroot(str(self))


    # --- Globbing

    @staticmethod
    def _id(stat):
        return (stat.st_ino, stat.st_dev)


class NTPath(BasePath):
    """ Represents paths on Windows operating systems. """

    class _NTBaseRoot(BasePath._BaseRoot):
        """ The base class of all Windows root classes. """
        pass

    _OSBaseRoot = _NTBaseRoot

    class _CurRootType(_NTBaseRoot):
        """ Represents the root of the current working drive.
        
        This class is a singleton. It represents the root of the current
        working drive - paths starting with '\'.
        """
        instance = None
        def __new__(cls):
            if cls.instance is None:
                instance = object.__new__(cls)
                cls.instance = instance
            return cls.instance
        
        def __str__(self):
            return '\\'

        def __repr__(self):
            return 'path.CURROOT'

        isabs = False

        def abspath(self):
            from nt import _getfullpathname
            return NTPath(_getfullpathname(str(self)))

    CURROOT = _CurRootType()

    class Drive(_NTBaseRoot):
        """ Represents the root of a specific drive. """
        def __init__(self, letter):
            # Drive letter is normalized - we don't lose any information
            if len(letter) != 1 or letter not in string.letters:
                raise ValueError, 'Should get one letter'
            self._letter = letter.lower()

        @property
        def letter(self):
            # We use a property because we want the object to be immutable.
            return self._letter

        def __str__(self):
            return '%s:\\' % self.letter

        def __repr__(self):
            return 'path.Drive(%r)' % self.letter

        isabs = True

    class UnrootedDrive(_NTBaseRoot):
        """ Represents the current working directory on a specific drive. """
        def __init__(self, letter):
            # Drive letter is normalized - we don't lose any information
            if len(letter) != 1 or letter not in string.letters:
                raise ValueError, 'Should get one letter'
            self._letter = letter.lower()

        @property
        def letter(self):
            # We use a property because we want the object to be immutable.
            return self._letter

        def __str__(self):
            return '%s:' % self.letter

        def __repr__(self):
            return 'path.UnrootedDrive(%r)' % self.letter

        isabs = False

        def abspath(self):
            from nt import _getfullpathname
            return NTPath(_getfullpathname(str(self)))

    class UNCRoot(_NTBaseRoot):
        """ Represents a UNC mount point. """
        def __init__(self, host, mountpoint):
            # Host and mountpoint are normalized - we don't lose any information
            self._host = host.lower()
            self._mountpoint = mountpoint.lower()

        @property
        def host(self):
            # We use a property because we want the object to be immutable.
            return self._host

        @property
        def mountpoint(self):
            # We use a property because we want the object to be immutable.
            return self._mountpoint

        def __str__(self):
            return '\\\\%s\\%s\\' % (self.host, self.mountpoint)

        def __repr__(self):
            return 'path.UNCRoot(%r, %r)' % (self.host, self.mountpoint)

        isabs = True
            
            
    # Public constants
    curdir = '.'
    pardir = '..'

    # Private constants
    _sep = '\\'
    _altsep = '/'

    @staticmethod
    def normcasestr(string):
        """ Normalize the case of one path element.
        
        On Windows, this returns string.lower()
        """
        return string.lower()

    @classmethod
    def _parse_str(cls, pathstr):
        # get a path string and return an iterable over path elements.

        # First, replace all backslashes with slashes.
        # I know that it should have been the other way round, but I can't
        # stand all those escapes.
        
        pathstr = pathstr.replace('\\', '/')

        # Handle the root element
        
        if pathstr.startswith('/'):
            if pathstr.startswith('//'):
                # UNC Path
                if pathstr.startswith('///'):
                    raise ValueError, \
                          "Paths can't start with more than two slashes"
                index = pathstr.find('/', 2)
                if index == -1:
                    raise ValueError, \
                          "UNC host name should end with a slash"
                index2 = index+1
                while pathstr[index2:index2+1] == '/':
                    index2 += 1
                if index2 == len(pathstr):
                    raise ValueError, \
                          "UNC mount point is empty"
                index3 = pathstr.find('/', index2)
                if index3 == -1:
                    index3 = len(pathstr)
                yield cls.UNCRoot(pathstr[2:index], pathstr[index2:index3])
                pathstr = pathstr[index3:]
            else:
                # CURROOT
                yield cls.CURROOT
        else:
            if pathstr[1:2] == ':':
                if pathstr[2:3] == '/':
                    # Rooted drive
                    yield cls.Drive(pathstr[0])
                    pathstr = pathstr[3:]
                else:
                    # Unrooted drive
                    yield cls.UnrootedDrive(pathstr[0])
                    pathstr = pathstr[2:]

        # Handle all other elements
        
        for element in pathstr.split('/'):
            if element == '' or element == cls.curdir:
                continue
            # We don't treat pardir specially, since in the presence of
            # links there's nothing to do about them.
            # Windows doesn't have links, but why not keep path handling
            # similiar?
            yield element


    # NT-specific methods

    # --- Extra

    def startfile(self):
        return os.startfile(str(self))

if os.name == 'posix':
    path = PosixPath
elif os.name == 'nt':
    path = NTPath
else:
    raise NotImplementedError, \
          "The path object is currently not implemented for OS %r" % os.name
   1 """ path.py - An object representing a path to a file or a directory.
   2 
   3 Based on the path module by Jason Orendorff
   4 (http://www.jorendorff.com/articles/python/path)
   5 
   6 Written by Noam Raphael to show the idea of using a tuple instead of
   7 a string, and to reduce the number of methods.
   8 
   9 Currently only implements posix and nt paths - more can be added.
  10 
  11 """
  12 
  13 import os
  14 import stat
  15 import itertools
  16 import fnmatch
  17 import re
  18 import string
  19 
  20 class StatWrapper(object):
  21     """ A wrapper around stat_result objects which gives additional properties.
  22     
  23     This object is a wrapper around a stat_result object. It allows access
  24     to all the original object's attributes, and adds a few convinient
  25     properties, by using the stat module.
  26     
  27     This object should have been a subclass posix.stat_result - it simply
  28     isn't possible currently. This functionality may also be integrated into
  29     the original type.
  30     """
  31     
  32     __slots__ = ['_stat']
  33     
  34     def __init__(self, stat):
  35         self._stat = stat
  36         
  37     def __getattribute__(self, attr, *default):
  38         try:
  39             return object.__getattribute__(self, attr, *default)
  40         except AttributeError:
  41             return getattr(self._stat, attr, *default)
  42 
  43     # Mode properties
  44     
  45     @property
  46     def isdir(self):
  47         return stat.S_ISDIR(self.st_mode)
  48     @property
  49     def isfile(self):
  50         return stat.S_ISREG(self.st_mode)
  51     @property
  52     def islink(self):
  53         return stat.S_ISLNK(self.st_mode)
  54     
  55     # Easier names properties
  56 
  57     @property
  58     def size(self):
  59         return self.st_size
  60     @property
  61     def mtime(self):
  62         return self.st_mtime
  63     @property
  64     def atime(self):
  65         return self.st_atime
  66     @property
  67     def ctime(self):
  68         return self.st_ctime
  69 
  70 
  71 class BasePath(tuple):
  72     """ The base, abstract, path type.
  73     
  74     The OS-specific path types inherit from it.
  75     """
  76 
  77     # ----------------------------------------------------------------
  78     # We start with methods which don't use system calls - they just
  79     # manipulate paths.
  80 
  81     class _BaseRoot(object):
  82         """ Represents a start location for a path.
  83         
  84         A Root is an object which may be the first element of a path tuple,
  85         and represents from where to start the path.
  86         
  87         On posix, there's only one: ROOT (singleton).
  88         On nt, there are a few:
  89           CURROOT - the root of the current drive (singleton)
  90           Drive(letter) - the root of a specific drive
  91           UnrootedDrive(letter) - the current working directory on a specific
  92                                   drive
  93           UNCRoot(host, mountpoint) - a UNC mount point
  94 
  95         The class for each OS has its own root classes, which should inherit
  96         from _OSBaseRoot.
  97 
  98         str(root) should return the string name of the root. The string should
  99         identify the root: two root elements with the same string should have
 100         the same meaning. To allow meaningful sorting of path objects, root
 101         objects can be compared to strings and other root objects. They are
 102         smaller than all strings, and are compared with other root objects
 103         according to their string name.
 104 
 105         Every Root object should contain the "isabs" attribute, which is True
 106         if changes in the current working directory won't change the meaning
 107         of the root and False otherwise. (CURROOT and UnrootedDrive aren't
 108         absolute)
 109         If isabs is True, it should also implement the abspath() method, which
 110         should return an absolute path object, equivalent to the root when the
 111         call was made.
 112         """
 113         isabs = None
 114 
 115         def abspath(self):
 116             if self.abspath:
 117                 raise NotImplementedError, 'This root is already absolute'
 118             else:
 119                 raise NotImplementedError, 'abspath is abstract'
 120 
 121         def __str__(self):
 122             raise NotImplementedError, '__str__ is abstract'
 123 
 124         def __cmp__(self, other):
 125             if isinstance(other, str):
 126                 return -1
 127             elif isinstance(other, BasePath._BaseRoot):
 128                 return cmp(str(self), str(other))
 129             else:
 130                 raise TypeError, 'Comparison not defined'
 131 
 132         def __hash__(self):
 133             # This allows path objects to be hashable
 134             return hash(str(self))
 135 
 136     # _OSBaseRoot should be the base of the OS-specific root classes, which
 137     # should inherit from _BaseRoot
 138     _OSBaseRoot = None
 139 
 140     # These string constants should be filled by subclasses - they are real
 141     # directory names
 142     curdir = None
 143     pardir = None
 144 
 145     # These string constants are used by default implementations of methods,
 146     # but are not part of the interface - the whole idea is for the interface
 147     # to hide those details.
 148     _sep = None
 149     _altsep = None
 150 
 151     @staticmethod
 152     def _parse_str(pathstr):
 153         # Concrete path classes should implement _parse_str to get a path
 154         # string and return an iterable over path elements.
 155         raise NotImplementedError, '_parse_str is abstract'
 156 
 157     @staticmethod
 158     def normcasestr(string):
 159         """ Normalize the case of one path element.
 160         
 161         This default implementation returns string unchanged. On
 162         case-insensitive platforms, it returns the normalized string.
 163         """
 164         return string
 165 
 166     # We make this method a property, to show that it doesn't use any
 167     # system calls.
 168     # Case-sensitive subclasses can redefine it to return self.
 169     @property
 170     def normcase(self):
 171         """ Return an equivalent path with case-normalized elements. """
 172         if self.isrel:
 173             return self.__class__(self.normcasestr(element)
 174                                   for element in self)
 175         else:
 176             def gen():
 177                 it = iter(self)
 178                 yield it.next()
 179                 for element in it:
 180                     yield self.normcasestr(element)
 181             return self.__class__(gen())
 182 
 183     @classmethod
 184     def _normalize_elements(cls, elements):
 185         # This method gets an iterable over path elements.
 186         # It should return an iterator over normalized path elements -
 187         # that is, curdir elements should be ignored.
 188         
 189         for i, element in enumerate(elements):
 190             if isinstance(element, str):
 191                 if element != cls.curdir:
 192                     if (not element or
 193                         cls._sep in element or
 194                         (cls._altsep and cls._altsep in element)):
 195                         # Those elements will cause path(str(x)) != x
 196                         raise ValueError, "Element %r is invalid" % element
 197                     yield element
 198             elif i == 0 and isinstance(element, cls._OSBaseRoot):
 199                 yield element
 200             else:
 201                 raise TypeError, "Element %r is of a wrong type" % element
 202 
 203     def __new__(cls, arg=None):
 204         """ Create a new path object.
 205         
 206         If arg isn't given, an empty path, which represents the current
 207         working directory, is returned.
 208         If arg is a string, it is parsed into a logical path.
 209         If arg is an iterable over path elements, a new path is created from
 210         them.
 211         """
 212         if arg is None:
 213             return tuple.__new__(cls)
 214         elif type(arg) is cls:
 215             return arg
 216         elif isinstance(arg, str):
 217             return tuple.__new__(cls, cls._parse_str(arg))
 218         else:
 219             return tuple.__new__(cls, cls._normalize_elements(arg))
 220 
 221     def __init__(self, arg=None):
 222         # Since paths are immutable, we can cache the string representation
 223         self._cached_str = None
 224 
 225     def _build_str(self):
 226         # Return a string representation of self.
 227         # 
 228         # This is a default implementation, which may be overriden by
 229         # subclasses (form example, MacPath)
 230         if not self:
 231             return self.curdir
 232         elif isinstance(self[0], self._OSBaseRoot):
 233             return str(self[0]) + self._sep.join(self[1:])
 234         else:
 235             return self._sep.join(self)
 236 
 237     def __str__(self):
 238         """ Return a string representation of self. """
 239         if self._cached_str is None:
 240             self._cached_str = self._build_str()
 241         return self._cached_str
 242 
 243     def __repr__(self):
 244         # We want path, not the real class name.
 245         return 'path(%r)' % str(self)
 246 
 247     @property
 248     def isabs(self):
 249         """ Return whether this path represent an absolute path.
 250 
 251         An absolute path is a path whose meaning doesn't change when the
 252         the current working directory changes.
 253         
 254         (Note that this is not the same as "not self.isrelative")
 255         """
 256         return len(self) > 0 and \
 257                isinstance(self[0], self._OSBaseRoot) and \
 258                self[0].isabs
 259 
 260     @property
 261     def isrel(self):
 262         """ Return whether this path represents a relative path.
 263 
 264         A relative path is a path without a root element, so it can be
 265         concatenated to other paths.
 266 
 267         (Note that this is not the same as "not self.isabs")
 268         """
 269         return len(self) == 0 or \
 270                not isinstance(self[0], self._OSBaseRoot)
 271 
 272     # Wrap a few tuple methods to return path objects
 273 
 274     def __add__(self, other):
 275         other = self.__class__(other)
 276         if not other.isrel:
 277             raise ValueError, "Right operand should be a relative path"
 278         return self.__class__(itertools.chain(self, other))
 279 
 280     def __radd__(self, other):
 281         if not self.isrel:
 282             raise ValueError, "Right operand should be a relative path"
 283         other = self.__class__(other)
 284         return self.__class__(itertools.chain(other, self))
 285 
 286     def __getslice__(self, *args):
 287         return self.__class__(tuple.__getslice__(self, *args))
 288 
 289     def __mul__(self, *args):
 290         if not self.isrel:
 291             raise ValueError, "Only relative paths can be multiplied"
 292         return self.__class__(tuple.__mul__(self, *args))
 293 
 294     def __rmul__(self, *args):
 295         if not self.isrel:
 296             raise ValueError, "Only relative paths can be multiplied"
 297         return self.__class__(tuple.__rmul__(self, *args))
 298 
 299     def __eq__(self, other):
 300         return tuple.__eq__(self, self.__class__(other))
 301     def __ge__(self, other):
 302         return tuple.__ge__(self, self.__class__(other))
 303     def __gt__(self, other):
 304         return tuple.__gt__(self, self.__class__(other))
 305     def __le__(self, other):
 306         return tuple.__le__(self, self.__class__(other))
 307     def __lt__(self, other):
 308         return tuple.__lt__(self, self.__class__(other))
 309     def __ne__(self, other):
 310         return tuple.__ne__(self, self.__class__(other))
 311         
 312 
 313     # ----------------------------------------------------------------
 314     # Now come the methods which use system calls.
 315 
 316     # --- Path transformation which use system calls
 317 
 318     @classmethod
 319     def cwd(cls):
 320         return cls(os.getcwd())
 321 
 322     def chdir(self):
 323         return os.chdir(str(self))
 324 
 325     def abspath(self):
 326         if not self:
 327             return self.cwd()
 328         if isinstance(self[0], self._OSBaseRoot):
 329             if self[0].isabs:
 330                 return self
 331             else:
 332                 return self[0].abspath() + self[1:]
 333         else:
 334             return self.cwd() + self
 335 
 336     def realpath(self):
 337         return self.__class__(os.path.realpath(str(self)))
 338 
 339     def relpathto(self, dst):
 340         """ Return a relative path from self to dest.
 341 
 342         This method examines self.realpath() and dest.realpath(). If
 343         they have the same root element, a path in the form
 344         path([path.pardir, path.pardir, ..., dir1, dir2, ...])
 345         is returned. If they have different root elements,
 346         dest.realpath() is returned.
 347         """
 348         src = self.realpath()
 349         dst = self.__class__(dst).realpath()
 350 
 351         if src[0] == dst[0]:
 352             # They have the same root
 353             
 354             # find the length of the equal prefix
 355             i = 1
 356             while i < len(src) and i < len(dst) and \
 357                   self.normcasestr(src[i]) == self.normcasestr(dst[i]):
 358                 i += 1
 359 
 360             return [self.pardir] * (len(src) - i) + dst[i:]
 361 
 362         else:
 363             # They don't have the same root
 364             return dst
 365             
 366 
 367     
 368 
 369     # --- Expand
 370 
 371     def expanduser(self):
 372         return path(os.path.expanduser(str(self)))
 373 
 374     def expandvars(self):
 375         return path(os.path.expandvars(str(self)))
 376     
 377 
 378     # --- Info about the path
 379 
 380     def stat(self):
 381         return StatWrapper(os.stat(str(self)))
 382     
 383     def exists(self):
 384         try:
 385             self.stat()
 386         except OSError:
 387             return False
 388         else:
 389             return True
 390 
 391     def isdir(self):
 392         try:
 393             return self.stat().isdir
 394         except OSError:
 395             return False
 396 
 397     def isfile(self):
 398         try:
 399             return self.stat().isfile
 400         except OSError:
 401             return False
 402         
 403     def lstat(self):
 404         return StatWrapper(os.lstat(str(self)))
 405 
 406     def lexists(self):
 407         try:
 408             self.lstat()
 409         except OSError:
 410             return False
 411         else:
 412             return True
 413 
 414     def lisdir(self):
 415         try:
 416             return self.stat().lisdir
 417         except OSError:
 418             return False
 419 
 420     def lisfile(self):
 421         try:
 422             return self.stat().lisfile
 423         except OSError:
 424             return False
 425 
 426     def islink(self):
 427         try:
 428             return self.lstat().islink
 429         except OSError:
 430             return False
 431         
 432     def ismount(self):
 433         return os.path.ismount(str(self))
 434 
 435     def access(self, mode):
 436         """ Return true if current user has access to this path.
 437 
 438         mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
 439         """
 440         return os.access(str(self), mode)
 441 
 442     # Additional methods in subclasses:
 443     # statvfs (PosixPath)
 444     # pathconf (PosixPath, XXX MacPath)
 445     # samefile (PosixPath, XXX MacPath)
 446 
 447 
 448     # --- Modifying operations on files and directories
 449 
 450     def utime(self, times):
 451         """ Set the access and modified times of this file. """
 452         os.utime(str(self), times)
 453 
 454     def chmod(self, mode):
 455         os.chmod(str(self), mode)
 456 
 457     def rename(self, new):
 458         os.rename(str(self), str(new))
 459 
 460     # Additional methods in subclasses:
 461     # chown (PosixPath, XXX MacPath)
 462     # lchown (PosixPath, XXX MacPath)
 463 
 464 
 465     # --- Create/delete operations on directories
 466 
 467     def mkdir(self, mode=0777):
 468         os.mkdir(str(self), mode)
 469 
 470     def makedirs(self, mode=0777):
 471         os.makedirs(str(self), mode)
 472 
 473     def rmdir(self):
 474         os.rmdir(str(self))
 475 
 476     def removedirs(self, base=None):
 477         """ Remove empty directories recursively.
 478         
 479         If the directory is empty, remove it. If the parent directory becomes
 480         empty, remove it too. Continue until a directory can't be removed,
 481         because it's not empty or for other reasons.
 482         If base is given, it should be a prefix of self. base won't be removed
 483         even if it becomes empty.
 484         Note: only directories explicitly listed in the path will be removed.
 485         This means that if self is a relative path, predecesors of the
 486         current working directory won't be removed.
 487         """
 488         if not self.stat().isdir:
 489             raise OSError, 'removedirs only works on directories.'
 490         base = self.__class__(base)
 491         if base:
 492             if not self[:len(base)] == base:
 493                 raise ValueError, 'base should be a prefix of self.'
 494             stopat = len(base)
 495         else:
 496             stopat = 0
 497         for i in xrange(len(self), stopat, -1):
 498             try:
 499                 self[:i].rmdir()
 500             except OSError:
 501                 break
 502 
 503     def rmtree(self, *args):
 504         return shutil.rmtree(str(self), *args)
 505 
 506 
 507     # --- Modifying operations on files
 508 
 509     def touch(self):
 510         """ Set the access/modified times of this file to the current time.
 511         Create the file if it does not exist.
 512         """
 513         fd = os.open(str(self), os.O_WRONLY | os.O_CREAT, 0666)
 514         os.close(fd)
 515         os.utime(str(self), None)
 516 
 517     def remove(self):
 518         os.remove(str(self))
 519 
 520     def copy(self, dst, copystat=False):
 521         """ Copy file from self to dst.
 522 
 523         If copystat is False, copy data and mode bits ("cp self dst").
 524         If copystat is True, copy data and all stat info ("cp -p self dst").
 525 
 526         The destination may be a directory. If so, a file with the same base
 527         name as self will be created in that directory.
 528         """
 529         dst = self.__class__(dst)
 530         if dst.stat().isdir:
 531             dst += self[-1]
 532         shutil.copyfile(str(self), str(dst))
 533         if copystat:
 534             shutil.copystat(str(self), str(dst))
 535         else:
 536             shutil.copymode(str(self), str(dst))
 537 
 538     def move(self, dst):
 539         dst = self.__class__(dst)
 540         return shutil.move(str(self), str(dst))
 541         
 542 
 543     # --- Links
 544 
 545     # In subclasses:
 546     # link (PosixPath, XXX MacPath)
 547     # writelink (PosixPath) - what about MacPath?
 548     # readlink (PosixPath, XXX MacPath)
 549     # readlinkpath (PosixPath, XXXMacPath)
 550 
 551 
 552     # --- Extra
 553 
 554     # In subclasses:
 555     # mkfifo (PosixPath, XXX MacPath)
 556     # mknod (PosixPath, XXX MacPath)
 557     # chroot (PosixPath, XXX MacPath)
 558     #
 559     # startfile (NTPath)
 560 
 561 
 562     # --- Globbing
 563 
 564     # If the OS supports it, _id should be a function that gets a stat object
 565     # and returns a unique id of a file.
 566     # It the OS doesn't support it, it should be None.
 567     _id = None
 568 
 569     @staticmethod
 570     def _match_element(comp_element, element):
 571         # Does a filename match a compiled pattern element?
 572         # The filename should be normcased.
 573         if comp_element is None:
 574             return True
 575         elif isinstance(comp_element, str):
 576             return comp_element == element
 577         else:
 578             return comp_element.match(element)
 579 
 580     def _glob(cls, pth, comp_pattern, topdown, onlydirs, onlyfiles,
 581               positions, on_path, stat):
 582         """ The recursive function used by glob.
 583 
 584         This version treats symbolic links as files. Broken symlinks won't be
 585         listed.
 586 
 587         pth is a dir in which we search.
 588         
 589         comp_pattern is the compiled pattern. It's a sequence which should
 590         consist of three kinds of elements:
 591         * None - matches any number of subdirectories, including 0.
 592         * a string - a normalized name, when exactly one name can be matched.
 593         * a regexp - for testing if normalized names match.
 594 
 595         positions is a sequence of positions on comp_pattern that children of
 596         path may match. On the first call, if will be [0].
 597 
 598         on_path is a set of inode identifiers on path, or None if circles
 599         shouldn't be checked.
 600 
 601         stat is the appropriate stat function - cls.stat or cls.lstat.
 602         """
 603 
 604         if len(positions) == 1 and isinstance(comp_pattern[positions[0]], str):
 605             # We don't have to listdir if exactly one file name can match.
 606             # Since we always stat the files, it's ok if the file doesn't exist.
 607             listdir = [comp_pattern[positions[0]]]
 608         else:
 609             listdir = os.listdir(str(pth))
 610             listdir.sort()
 611 
 612         for subfile in listdir:
 613             newpth = pth + subfile
 614             # We don't strictly need to stat a file if we don't follow symlinks
 615             # AND positions == [len(comp_pattern)-1] AND
 616             # not isinstance(comp_pattern[-1], str), but do me a favour...
 617             try:
 618                 st = stat(newpth)
 619             except OSError:
 620                 continue
 621             newpositions = []
 622             subfilenorm = cls.normcasestr(subfile)
 623             
 624             if topdown:
 625                 # If not topdown, it happens after we handle subdirs
 626                 if positions[-1] == len(comp_pattern) - 1:
 627                     if cls._match_element(comp_pattern[-1], subfilenorm):
 628                         if not ((onlydirs and not st.isdir) or
 629                                 (onlyfiles and not st.isfile)):
 630                             yield newpth
 631 
 632             for pos in reversed(positions):
 633                 if st.isdir:
 634                     comp_element = comp_pattern[pos]
 635                     if pos + 1 < len(comp_pattern):
 636                         if cls._match_element(comp_element, subfilenorm):
 637                             newpositions.append(pos + 1)
 638                             if comp_pattern[pos + 1] is None:
 639                                 # We should stop at '..'
 640                                 break
 641                     if comp_element is None:
 642                         newpositions.append(pos)
 643                         # We don't have to break - there are not supposed
 644                         # to be any positions before '..'.
 645 
 646             if newpositions:
 647                 newpositions.reverse()
 648 
 649                 if on_path is not None:
 650                     newpath_id = cls._id(st)
 651                     if newpath_id in on_path:
 652                         raise OSError, "Circular path encountered"
 653                     on_path.add(newpath_id)
 654 
 655                 for x in cls._glob(newpth,
 656                                    comp_pattern, topdown, onlydirs, onlyfiles,
 657                                    newpositions, on_path, stat):
 658                     yield x
 659 
 660                 if on_path is not None:
 661                     on_path.remove(newpath_id)
 662 
 663             if not topdown:
 664                 # If topdown, it happens after we handle subdirs
 665                 if positions[-1] == len(comp_pattern) - 1:
 666                     if cls._match_element(comp_pattern[-1], subfilenorm):
 667                         if not ((onlydirs and not st.isdir) or
 668                                 (onlyfiles and not st.isfile)):
 669                             yield newpth
 670 
 671     _magic_check = re.compile('[*?[]')
 672 
 673     @classmethod
 674     def _has_magic(cls, s):
 675         return cls._magic_check.search(s) is not None
 676 
 677     _cache = {}
 678 
 679     @classmethod
 680     def _compile_pattern(cls, pattern):
 681         # Get a pattern, return the list of compiled pattern elements
 682         # and the list of initial positions.
 683         pattern = cls(pattern)
 684         if not pattern.isrel:
 685             raise ValueError, "pattern should be a relative path."
 686 
 687         comp_pattern = []
 688         last_was_none = False
 689         for element in pattern:
 690             element = cls.normcasestr(element)
 691             if element == '**':
 692                 if not last_was_none:
 693                     comp_pattern.append(None)
 694             else:
 695                 last_was_none = False
 696                 if not cls._has_magic(element):
 697                     comp_pattern.append(element)
 698                 else:
 699                     try:
 700                         r = cls._cache[element]
 701                     except KeyError:
 702                         r = re.compile(fnmatch.translate(element))
 703                         cls._cache[element] = r
 704                     comp_pattern.append(r)
 705 
 706         if comp_pattern[0] is None and len(comp_pattern) > 1:
 707             positions = [0, 1]
 708         else:
 709             positions = [0]
 710 
 711         return comp_pattern, positions
 712 
 713     def match(self, pattern):
 714         """ Return whether self matches the given pattern.
 715 
 716         pattern has the same meaning as in the glob method.
 717         self should be relative.
 718 
 719         This method doesn't use any system calls.
 720         """
 721         if not self.isrel:
 722             raise ValueError, "self must be a relative path"
 723         comp_pattern, positions = self._compile_pattern(pattern)
 724 
 725         for element in self.normcase:
 726             newpositions = []
 727             for pos in reversed(positions):
 728                 if pos == len(comp_pattern):
 729                     # We matched the pattern but the path isn't finished -
 730                     # too bad
 731                     continue
 732                 comp_element = comp_pattern[pos]
 733                 if self._match_element(comp_element, element):
 734                     newpositions.append(pos + 1)
 735                 if comp_element is None:
 736                     newpositions.append(pos)
 737                     # No need to continue after a '**'
 738                     break
 739             newpositions.reverse()
 740             positions = newpositions
 741             if not positions:
 742                 # No point in carrying on
 743                 break
 744 
 745         return (len(comp_pattern) in positions)
 746 
 747     def glob(self, pattern='*', topdown=True, onlydirs=False, onlyfiles=False):
 748         """ Return an iterator over all files in self matching pattern.
 749 
 750         pattern should be a relative path, which may include wildcards.
 751         In addition to the regular shell wildcards, you can use '**', which
 752         matches any number of directories, including 0.
 753 
 754         If topdown is True (the default), a directory is yielded before its
 755         descendents. If it's False, a directory is yielded after its
 756         descendents.
 757 
 758         If onlydirs is True, only directories will be yielded. If onlyfiles
 759         is True, only regular files will be yielded.
 760 
 761         This method treats symbolic links as regular files. Broken symlinks
 762         won't be yielded.
 763         """
 764 
 765         if onlydirs and onlyfiles:
 766             raise ValueError, \
 767                   "Only one of onlydirs and onlyfiles can be specified."
 768 
 769         comp_pattern, positions = self._compile_pattern(pattern)
 770 
 771         if self._id is not None and None in comp_pattern:
 772             on_path = set([self._id(self.stat())])
 773         else:
 774             on_path = None
 775             
 776         for x in self._glob(self, comp_pattern, topdown, onlydirs, onlyfiles,
 777                             positions, on_path, self.__class__.stat):
 778             yield x
 779         
 780     def lglob(self, pattern='*', topdown=True, onlydirs=False, onlyfiles=False):
 781         """ Return an iterator over all files in self matching pattern.
 782 
 783         pattern should be a relative path, which may include wildcards.
 784         In addition to the regular shell wildcards, you can use '**', which
 785         matches any number of directories, including 0.
 786 
 787         If topdown is True (the default), a directory is yielded before its
 788         descendents. If it's False, a directory is yielded after its
 789         descendents.
 790 
 791         If onlydirs is True, only directories will be yielded. If onlyfiles
 792         is True, only regular files will be yielded.
 793 
 794         This method treats symbolic links as special files - they won't be
 795         followed, and they will be yielded even if they're broken.
 796         """
 797 
 798         if onlydirs and onlyfiles:
 799             raise ValueError, \
 800                   "Only one of onlydirs and onlyfiles can be specified."
 801 
 802         comp_pattern, positions = self._compile_pattern(pattern)
 803             
 804         for x in self._glob(self, comp_pattern, topdown, onlydirs, onlyfiles,
 805                             positions, None, self.__class__.lstat):
 806             yield x
 807 
 808 
 809 class PosixPath(BasePath):
 810     """ Represents POSIX paths. """
 811     
 812     class _PosixRoot(BasePath._BaseRoot):
 813         """ Represents the filesystem root (/).
 814         
 815         There's only one root on posix systems, so this is a singleton.
 816         """
 817         instance = None
 818         def __new__(cls):
 819             if cls.instance is None:
 820                 instance = object.__new__(cls)
 821                 cls.instance = instance
 822             return cls.instance
 823         
 824         def __str__(self):
 825             return '/'
 826 
 827         def __repr__(self):
 828             return 'path.ROOT'
 829 
 830         isabs = True
 831 
 832     _OSBaseRoot = _PosixRoot
 833 
 834     ROOT = _PosixRoot()
 835 
 836     # Public constants
 837     curdir = '.'
 838     pardir = '..'
 839 
 840     # Private constants
 841     _sep = '/'
 842     _altsep = None
 843 
 844     @classmethod
 845     def _parse_str(cls, pathstr):
 846         # get a path string and return an iterable over path elements.
 847         if pathstr.startswith('/'):
 848             if pathstr.startswith('//') and not pathstr.startswith('///'):
 849                 # Two initial slashes have application-specific meaning
 850                 # in POSIX, and it's not supported currently.
 851                 raise NotImplementedError, \
 852                       "Paths with two leading slashes aren't supported."
 853             yield cls.ROOT
 854         for element in pathstr.split('/'):
 855             if element == '' or element == cls.curdir:
 856                 continue
 857             # '..' aren't specially treated, since popping the last
 858             # element isn't correct if the last element was a symbolic
 859             # link.
 860             yield element
 861 
 862 
 863     # POSIX-specific methods
 864     
 865 
 866     # --- Info about the path
 867 
 868     def statvfs(self):
 869         """ Perform a statvfs() system call on this path. """
 870         return os.statvfs(str(self))
 871 
 872     def pathconf(self, name):
 873         return os.pathconf(str(self), name)
 874 
 875     def samefile(self, other):
 876         other = self.__class__(other)
 877         s1 = self.stat()
 878         s2 = other.stat()
 879         return s1.st_ino == s2.st_ino and \
 880                s1.st_dev == s2.st_dev
 881 
 882 
 883     # --- Modifying operations on files and directories
 884 
 885     def chown(self, uid=None, gid=None):
 886         if uid is None:
 887             uid = -1
 888         if gid is None:
 889             gid = -1
 890         return os.chown(str(self), uid, gid)
 891     
 892     def lchown(self, uid=None, gid=None):
 893         if uid is None:
 894             uid = -1
 895         if gid is None:
 896             gid = -1
 897         return os.lchown(str(self), uid, gid)
 898 
 899 
 900     # --- Links
 901 
 902     def link(self, newpath):
 903         """ Create a hard link at 'newpath', pointing to this file. """
 904         os.link(str(self), str(newpath))
 905 
 906     def writelink(self, src):
 907         """ Create a symbolic link at self, pointing to src.
 908 
 909         src may be any string. Note that if it's a relative path, it
 910         will be interpreted relative to self, not relative to the current
 911         working directory.
 912         """
 913         os.symlink(str(src), str(self))
 914 
 915     def readlink(self):
 916         """ Return the path to which this symbolic link points.
 917 
 918         The result is a string, which may be an absolute path, a
 919         relative path (which should be interpreted relative to self[:-1]),
 920         or any arbitrary string.
 921         """
 922         return os.readlink(str(self))
 923 
 924     def readlinkpath(self):
 925         """ Return the path to which this symbolic link points. """
 926         linkpath = self.__class__(self.readlink())
 927         if linkpath.isrel:
 928             return self + linkpath
 929         else:
 930             return linkpath
 931 
 932 
 933     # --- Extra
 934 
 935     def mkfifo(self, *args):
 936         return os.mkfifo(str(self), *args)
 937 
 938     def mknod(self, *args):
 939         return os.mknod(str(self), *args)
 940 
 941     def chroot(self):
 942         return os.chroot(str(self))
 943 
 944 
 945     # --- Globbing
 946 
 947     @staticmethod
 948     def _id(stat):
 949         return (stat.st_ino, stat.st_dev)
 950 
 951 
 952 class NTPath(BasePath):
 953     """ Represents paths on Windows operating systems. """
 954 
 955     class _NTBaseRoot(BasePath._BaseRoot):
 956         """ The base class of all Windows root classes. """
 957         pass
 958 
 959     _OSBaseRoot = _NTBaseRoot
 960 
 961     class _CurRootType(_NTBaseRoot):
 962         """ Represents the root of the current working drive.
 963         
 964         This class is a singleton. It represents the root of the current
 965         working drive - paths starting with '\'.
 966         """
 967         instance = None
 968         def __new__(cls):
 969             if cls.instance is None:
 970                 instance = object.__new__(cls)
 971                 cls.instance = instance
 972             return cls.instance
 973         
 974         def __str__(self):
 975             return '\\'
 976 
 977         def __repr__(self):
 978             return 'path.CURROOT'
 979 
 980         isabs = False
 981 
 982         def abspath(self):
 983             from nt import _getfullpathname
 984             return NTPath(_getfullpathname(str(self)))
 985 
 986     CURROOT = _CurRootType()
 987 
 988     class Drive(_NTBaseRoot):
 989         """ Represents the root of a specific drive. """
 990         def __init__(self, letter):
 991             # Drive letter is normalized - we don't lose any information
 992             if len(letter) != 1 or letter not in string.letters:
 993                 raise ValueError, 'Should get one letter'
 994             self._letter = letter.lower()
 995 
 996         @property
 997         def letter(self):
 998             # We use a property because we want the object to be immutable.
 999             return self._letter
1000 
1001         def __str__(self):
1002             return '%s:\\' % self.letter
1003 
1004         def __repr__(self):
1005             return 'path.Drive(%r)' % self.letter
1006 
1007         isabs = True
1008 
1009     class UnrootedDrive(_NTBaseRoot):
1010         """ Represents the current working directory on a specific drive. """
1011         def __init__(self, letter):
1012             # Drive letter is normalized - we don't lose any information
1013             if len(letter) != 1 or letter not in string.letters:
1014                 raise ValueError, 'Should get one letter'
1015             self._letter = letter.lower()
1016 
1017         @property
1018         def letter(self):
1019             # We use a property because we want the object to be immutable.
1020             return self._letter
1021 
1022         def __str__(self):
1023             return '%s:' % self.letter
1024 
1025         def __repr__(self):
1026             return 'path.UnrootedDrive(%r)' % self.letter
1027 
1028         isabs = False
1029 
1030         def abspath(self):
1031             from nt import _getfullpathname
1032             return NTPath(_getfullpathname(str(self)))
1033 
1034     class UNCRoot(_NTBaseRoot):
1035         """ Represents a UNC mount point. """
1036         def __init__(self, host, mountpoint):
1037             # Host and mountpoint are normalized - we don't lose any information
1038             self._host = host.lower()
1039             self._mountpoint = mountpoint.lower()
1040 
1041         @property
1042         def host(self):
1043             # We use a property because we want the object to be immutable.
1044             return self._host
1045 
1046         @property
1047         def mountpoint(self):
1048             # We use a property because we want the object to be immutable.
1049             return self._mountpoint
1050 
1051         def __str__(self):
1052             return '\\\\%s\\%s\\' % (self.host, self.mountpoint)
1053 
1054         def __repr__(self):
1055             return 'path.UNCRoot(%r, %r)' % (self.host, self.mountpoint)
1056 
1057         isabs = True
1058             
1059             
1060     # Public constants
1061     curdir = '.'
1062     pardir = '..'
1063 
1064     # Private constants
1065     _sep = '\\'
1066     _altsep = '/'
1067 
1068     @staticmethod
1069     def normcasestr(string):
1070         """ Normalize the case of one path element.
1071         
1072         On Windows, this returns string.lower()
1073         """
1074         return string.lower()
1075 
1076     @classmethod
1077     def _parse_str(cls, pathstr):
1078         # get a path string and return an iterable over path elements.
1079 
1080         # First, replace all backslashes with slashes.
1081         # I know that it should have been the other way round, but I can't
1082         # stand all those escapes.
1083         
1084         pathstr = pathstr.replace('\\', '/')
1085 
1086         # Handle the root element
1087         
1088         if pathstr.startswith('/'):
1089             if pathstr.startswith('//'):
1090                 # UNC Path
1091                 if pathstr.startswith('///'):
1092                     raise ValueError, \
1093                           "Paths can't start with more than two slashes"
1094                 index = pathstr.find('/', 2)
1095                 if index == -1:
1096                     raise ValueError, \
1097                           "UNC host name should end with a slash"
1098                 index2 = index+1
1099                 while pathstr[index2:index2+1] == '/':
1100                     index2 += 1
1101                 if index2 == len(pathstr):
1102                     raise ValueError, \
1103                           "UNC mount point is empty"
1104                 index3 = pathstr.find('/', index2)
1105                 if index3 == -1:
1106                     index3 = len(pathstr)
1107                 yield cls.UNCRoot(pathstr[2:index], pathstr[index2:index3])
1108                 pathstr = pathstr[index3:]
1109             else:
1110                 # CURROOT
1111                 yield cls.CURROOT
1112         else:
1113             if pathstr[1:2] == ':':
1114                 if pathstr[2:3] == '/':
1115                     # Rooted drive
1116                     yield cls.Drive(pathstr[0])
1117                     pathstr = pathstr[3:]
1118                 else:
1119                     # Unrooted drive
1120                     yield cls.UnrootedDrive(pathstr[0])
1121                     pathstr = pathstr[2:]
1122 
1123         # Handle all other elements
1124         
1125         for element in pathstr.split('/'):
1126             if element == '' or element == cls.curdir:
1127                 continue
1128             # We don't treat pardir specially, since in the presence of
1129             # links there's nothing to do about them.
1130             # Windows doesn't have links, but why not keep path handling
1131             # similiar?
1132             yield element
1133 
1134 
1135     # NT-specific methods
1136 
1137     # --- Extra
1138 
1139     def startfile(self):
1140         return os.startfile(str(self))
1141 
1142 if os.name == 'posix':
1143     path = PosixPath
1144 elif os.name == 'nt':
1145     path = NTPath
1146 else:
1147     raise NotImplementedError, \
1148           "The path object is currently not implemented for OS %r" % os.name

AlternativePathModule (last edited 2008-12-17 21:18:52 by PaulBoddie)

Unable to edit the page? See the FrontPage for instructions.