Revision 1 as of 2006-05-02 13:56:49

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

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