skip to navigation
skip to content

AlternativePathModule

   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 \
               isinstance(self[0], self._OSBaseRoot) and \
               self[0].isabs
 258 
 259     @property
 260     def isrel(self):
 261         """ Return whether this path represents a relative path.
 262 
 263         A relative path is a path without a root element, so it can be
 264         concatenated to other paths.
 265 
 266         (Note that this is not the same as "not self.isabs")
 267         """
 268         return len(self) == 0 or \
               not isinstance(self[0], self._OSBaseRoot)
 269 
 270     # Wrap a few tuple methods to return path objects
 271 
 272     def __add__(self, other):
 273         other = self.__class__(other)
 274         if not other.isrel:
 275             raise ValueError, "Right operand should be a relative path"
 276         return self.__class__(itertools.chain(self, other))
 277 
 278     def __radd__(self, other):
 279         if not self.isrel:
 280             raise ValueError, "Right operand should be a relative path"
 281         other = self.__class__(other)
 282         return self.__class__(itertools.chain(other, self))
 283 
 284     def __getslice__(self, *args):
 285         return self.__class__(tuple.__getslice__(self, *args))
 286 
 287     def __mul__(self, *args):
 288         if not self.isrel:
 289             raise ValueError, "Only relative paths can be multiplied"
 290         return self.__class__(tuple.__mul__(self, *args))
 291 
 292     def __rmul__(self, *args):
 293         if not self.isrel:
 294             raise ValueError, "Only relative paths can be multiplied"
 295         return self.__class__(tuple.__rmul__(self, *args))
 296 
 297     def __eq__(self, other):
 298         return tuple.__eq__(self, self.__class__(other))
 299     def __ge__(self, other):
 300         return tuple.__ge__(self, self.__class__(other))
 301     def __gt__(self, other):
 302         return tuple.__gt__(self, self.__class__(other))
 303     def __le__(self, other):
 304         return tuple.__le__(self, self.__class__(other))
 305     def __lt__(self, other):
 306         return tuple.__lt__(self, self.__class__(other))
 307     def __ne__(self, other):
 308         return tuple.__ne__(self, self.__class__(other))
 309 
 310 
 311     # ----------------------------------------------------------------
 312     # Now come the methods which use system calls.
 313 
 314     # --- Path transformation which use system calls
 315 
 316     @classmethod
 317     def cwd(cls):
 318         return cls(os.getcwd())
 319 
 320     def chdir(self):
 321         return os.chdir(str(self))
 322 
 323     def abspath(self):
 324         if not self:
 325             return self.cwd()
 326         if isinstance(self[0], self._OSBaseRoot):
 327             if self[0].isabs:
 328                 return self
 329             else:
 330                 return self[0].abspath() + self[1:]
 331         else:
 332             return self.cwd() + self
 333 
 334     def realpath(self):
 335         return self.__class__(os.path.realpath(str(self)))
 336 
 337     def relpathto(self, dst):
 338         """ Return a relative path from self to dest.
 339 
 340         This method examines self.realpath() and dest.realpath(). If
 341         they have the same root element, a path in the form
 342         path([path.pardir, path.pardir, ..., dir1, dir2, ...])
 343         is returned. If they have different root elements,
 344         dest.realpath() is returned.
 345         """
 346         src = self.realpath()
 347         dst = self.__class__(dst).realpath()
 348 
 349         if src[0] == dst[0]:
 350             # They have the same root
 351 
 352             # find the length of the equal prefix
 353             i = 1
 354             while i < len(src) and i < len(dst) and \
                  self.normcasestr(src[i]) == self.normcasestr(dst[i]):
 355                 i += 1
 356 
 357             return [self.pardir] * (len(src) - i) + dst[i:]
 358 
 359         else:
 360             # They don't have the same root
 361             return dst
 362 
 363 
 364 
 365 
 366     # --- Expand
 367 
 368     def expanduser(self):
 369         return path(os.path.expanduser(str(self)))
 370 
 371     def expandvars(self):
 372         return path(os.path.expandvars(str(self)))
 373 
 374 
 375     # --- Info about the path
 376 
 377     def stat(self):
 378         return StatWrapper(os.stat(str(self)))
 379 
 380     def exists(self):
 381         try:
 382             self.stat()
 383         except OSError:
 384             return False
 385         else:
 386             return True
 387 
 388     def isdir(self):
 389         try:
 390             return self.stat().isdir
 391         except OSError:
 392             return False
 393 
 394     def isfile(self):
 395         try:
 396             return self.stat().isfile
 397         except OSError:
 398             return False
 399 
 400     def lstat(self):
 401         return StatWrapper(os.lstat(str(self)))
 402 
 403     def lexists(self):
 404         try:
 405             self.lstat()
 406         except OSError:
 407             return False
 408         else:
 409             return True
 410 
 411     def lisdir(self):
 412         try:
 413             return self.stat().lisdir
 414         except OSError:
 415             return False
 416 
 417     def lisfile(self):
 418         try:
 419             return self.stat().lisfile
 420         except OSError:
 421             return False
 422 
 423     def islink(self):
 424         try:
 425             return self.lstat().islink
 426         except OSError:
 427             return False
 428 
 429     def ismount(self):
 430         return os.path.ismount(str(self))
 431 
 432     def access(self, mode):
 433         """ Return true if current user has access to this path.
 434 
 435         mode - One of the constants os.F_OK, os.R_OK, os.W_OK, os.X_OK
 436         """
 437         return os.access(str(self), mode)
 438 
 439     # Additional methods in subclasses:
 440     # statvfs (PosixPath)
 441     # pathconf (PosixPath, XXX MacPath)
 442     # samefile (PosixPath, XXX MacPath)
 443 
 444 
 445     # --- Modifying operations on files and directories
 446 
 447     def utime(self, times):
 448         """ Set the access and modified times of this file. """
 449         os.utime(str(self), times)
 450 
 451     def chmod(self, mode):
 452         os.chmod(str(self), mode)
 453 
 454     def rename(self, new):
 455         os.rename(str(self), str(new))
 456 
 457     # Additional methods in subclasses:
 458     # chown (PosixPath, XXX MacPath)
 459     # lchown (PosixPath, XXX MacPath)
 460 
 461 
 462     # --- Create/delete operations on directories
 463 
 464     def mkdir(self, mode=0777):
 465         os.mkdir(str(self), mode)
 466 
 467     def makedirs(self, mode=0777):
 468         os.makedirs(str(self), mode)
 469 
 470     def rmdir(self):
 471         os.rmdir(str(self))
 472 
 473     def removedirs(self, base=None):
 474         """ Remove empty directories recursively.
 475         
 476         If the directory is empty, remove it. If the parent directory becomes
 477         empty, remove it too. Continue until a directory can't be removed,
 478         because it's not empty or for other reasons.
 479         If base is given, it should be a prefix of self. base won't be removed
 480         even if it becomes empty.
 481         Note: only directories explicitly listed in the path will be removed.
 482         This means that if self is a relative path, predecesors of the
 483         current working directory won't be removed.
 484         """
 485         if not self.stat().isdir:
 486             raise OSError, 'removedirs only works on directories.'
 487         base = self.__class__(base)
 488         if base:
 489             if not self[:len(base)] == base:
 490                 raise ValueError, 'base should be a prefix of self.'
 491             stopat = len(base)
 492         else:
 493             stopat = 0
 494         for i in xrange(len(self), stopat, -1):
 495             try:
 496                 self[:i].rmdir()
 497             except OSError:
 498                 break
 499 
 500     def rmtree(self, *args):
 501         return shutil.rmtree(str(self), *args)
 502 
 503 
 504     # --- Modifying operations on files
 505 
 506     def touch(self):
 507         """ Set the access/modified times of this file to the current time.
 508         Create the file if it does not exist.
 509         """
 510         fd = os.open(str(self), os.O_WRONLY | os.O_CREAT, 0666)
 511         os.close(fd)
 512         os.utime(str(self), None)
 513 
 514     def remove(self):
 515         os.remove(str(self))
 516 
 517     def copy(self, dst, copystat=False):
 518         """ Copy file from self to dst.
 519 
 520         If copystat is False, copy data and mode bits ("cp self dst").
 521         If copystat is True, copy data and all stat info ("cp -p self dst").
 522 
 523         The destination may be a directory. If so, a file with the same base
 524         name as self will be created in that directory.
 525         """
 526         dst = self.__class__(dst)
 527         if dst.stat().isdir:
 528             dst += self[-1]
 529         shutil.copyfile(str(self), str(dst))
 530         if copystat:
 531             shutil.copystat(str(self), str(dst))
 532         else:
 533             shutil.copymode(str(self), str(dst))
 534 
 535     def move(self, dst):
 536         dst = self.__class__(dst)
 537         return shutil.move(str(self), str(dst))
 538 
 539 
 540     # --- Links
 541 
 542     # In subclasses:
 543     # link (PosixPath, XXX MacPath)
 544     # writelink (PosixPath) - what about MacPath?
 545     # readlink (PosixPath, XXX MacPath)
 546     # readlinkpath (PosixPath, XXXMacPath)
 547 
 548 
 549     # --- Extra
 550 
 551     # In subclasses:
 552     # mkfifo (PosixPath, XXX MacPath)
 553     # mknod (PosixPath, XXX MacPath)
 554     # chroot (PosixPath, XXX MacPath)
 555     #
 556     # startfile (NTPath)
 557 
 558 
 559     # --- Globbing
 560 
 561     # If the OS supports it, _id should be a function that gets a stat object
 562     # and returns a unique id of a file.
 563     # It the OS doesn't support it, it should be None.
 564     _id = None
 565 
 566     @staticmethod
 567     def _match_element(comp_element, element):
 568         # Does a filename match a compiled pattern element?
 569         # The filename should be normcased.
 570         if comp_element is None:
 571             return True
 572         elif isinstance(comp_element, str):
 573             return comp_element == element
 574         else:
 575             return comp_element.match(element)
 576 
 577     def _glob(cls, pth, comp_pattern, topdown, onlydirs, onlyfiles,
 578               positions, on_path, stat):
 579         """ The recursive function used by glob.
 580 
 581         This version treats symbolic links as files. Broken symlinks won't be
 582         listed.
 583 
 584         pth is a dir in which we search.
 585         
 586         comp_pattern is the compiled pattern. It's a sequence which should
 587         consist of three kinds of elements:
 588         * None - matches any number of subdirectories, including 0.
 589         * a string - a normalized name, when exactly one name can be matched.
 590         * a regexp - for testing if normalized names match.
 591 
 592         positions is a sequence of positions on comp_pattern that children of
 593         path may match. On the first call, if will be [0].
 594 
 595         on_path is a set of inode identifiers on path, or None if circles
 596         shouldn't be checked.
 597 
 598         stat is the appropriate stat function - cls.stat or cls.lstat.
 599         """
 600 
 601         if len(positions) == 1 and isinstance(comp_pattern[positions[0]], str):
 602             # We don't have to listdir if exactly one file name can match.
 603             # Since we always stat the files, it's ok if the file doesn't exist.
 604             listdir = [comp_pattern[positions[0]]]
 605         else:
 606             listdir = os.listdir(str(pth))
 607             listdir.sort()
 608 
 609         for subfile in listdir:
 610             newpth = pth + subfile
 611             # We don't strictly need to stat a file if we don't follow symlinks
 612             # AND positions == [len(comp_pattern)-1] AND
 613             # not isinstance(comp_pattern[-1], str), but do me a favour...
 614             try:
 615                 st = stat(newpth)
 616             except OSError:
 617                 continue
 618             newpositions = []
 619             subfilenorm = cls.normcasestr(subfile)
 620 
 621             if topdown:
 622                 # If not topdown, it happens after we handle subdirs
 623                 if positions[-1] == len(comp_pattern) - 1:
 624                     if cls._match_element(comp_pattern[-1], subfilenorm):
 625                         if not ((onlydirs and not st.isdir) or
 626                                 (onlyfiles and not st.isfile)):
 627                             yield newpth
 628 
 629             for pos in reversed(positions):
 630                 if st.isdir:
 631                     comp_element = comp_pattern[pos]
 632                     if pos + 1 < len(comp_pattern):
 633                         if cls._match_element(comp_element, subfilenorm):
 634                             newpositions.append(pos + 1)
 635                             if comp_pattern[pos + 1] is None:
 636                                 # We should stop at '..'
 637                                 break
 638                     if comp_element is None:
 639                         newpositions.append(pos)
 640                         # We don't have to break - there are not supposed
 641                         # to be any positions before '..'.
 642 
 643             if newpositions:
 644                 newpositions.reverse()
 645 
 646                 if on_path is not None:
 647                     newpath_id = cls._id(st)
 648                     if newpath_id in on_path:
 649                         raise OSError, "Circular path encountered"
 650                     on_path.add(newpath_id)
 651 
 652                 for x in cls._glob(newpth,
 653                                    comp_pattern, topdown, onlydirs, onlyfiles,
 654                                    newpositions, on_path, stat):
 655                     yield x
 656 
 657                 if on_path is not None:
 658                     on_path.remove(newpath_id)
 659 
 660             if not topdown:
 661                 # If topdown, it happens after we handle subdirs
 662                 if positions[-1] == len(comp_pattern) - 1:
 663                     if cls._match_element(comp_pattern[-1], subfilenorm):
 664                         if not ((onlydirs and not st.isdir) or
 665                                 (onlyfiles and not st.isfile)):
 666                             yield newpth
 667 
 668     _magic_check = re.compile('[*?[]')
 669 
 670     @classmethod
 671     def _has_magic(cls, s):
 672         return cls._magic_check.search(s) is not None
 673 
 674     _cache = {}
 675 
 676     @classmethod
 677     def _compile_pattern(cls, pattern):
 678         # Get a pattern, return the list of compiled pattern elements
 679         # and the list of initial positions.
 680         pattern = cls(pattern)
 681         if not pattern.isrel:
 682             raise ValueError, "pattern should be a relative path."
 683 
 684         comp_pattern = []
 685         last_was_none = False
 686         for element in pattern:
 687             element = cls.normcasestr(element)
 688             if element == '**':
 689                 if not last_was_none:
 690                     comp_pattern.append(None)
 691             else:
 692                 last_was_none = False
 693                 if not cls._has_magic(element):
 694                     comp_pattern.append(element)
 695                 else:
 696                     try:
 697                         r = cls._cache[element]
 698                     except KeyError:
 699                         r = re.compile(fnmatch.translate(element))
 700                         cls._cache[element] = r
 701                     comp_pattern.append(r)
 702 
 703         if comp_pattern[0] is None and len(comp_pattern) > 1:
 704             positions = [0, 1]
 705         else:
 706             positions = [0]
 707 
 708         return comp_pattern, positions
 709 
 710     def match(self, pattern):
 711         """ Return whether self matches the given pattern.
 712 
 713         pattern has the same meaning as in the glob method.
 714         self should be relative.
 715 
 716         This method doesn't use any system calls.
 717         """
 718         if not self.isrel:
 719             raise ValueError, "self must be a relative path"
 720         comp_pattern, positions = self._compile_pattern(pattern)
 721 
 722         for element in self.normcase:
 723             newpositions = []
 724             for pos in reversed(positions):
 725                 if pos == len(comp_pattern):
 726                     # We matched the pattern but the path isn't finished -
 727                     # too bad
 728                     continue
 729                 comp_element = comp_pattern[pos]
 730                 if self._match_element(comp_element, element):
 731                     newpositions.append(pos + 1)
 732                 if comp_element is None:
 733                     newpositions.append(pos)
 734                     # No need to continue after a '**'
 735                     break
 736             newpositions.reverse()
 737             positions = newpositions
 738             if not positions:
 739                 # No point in carrying on
 740                 break
 741 
 742         return (len(comp_pattern) in positions)
 743 
 744     def glob(self, pattern='*', topdown=True, onlydirs=False, onlyfiles=False):
 745         """ Return an iterator over all files in self matching pattern.
 746 
 747         pattern should be a relative path, which may include wildcards.
 748         In addition to the regular shell wildcards, you can use '**', which
 749         matches any number of directories, including 0.
 750 
 751         If topdown is True (the default), a directory is yielded before its
 752         descendents. If it's False, a directory is yielded after its
 753         descendents.
 754 
 755         If onlydirs is True, only directories will be yielded. If onlyfiles
 756         is True, only regular files will be yielded.
 757 
 758         This method treats symbolic links as regular files. Broken symlinks
 759         won't be yielded.
 760         """
 761 
 762         if onlydirs and onlyfiles:
 763             raise ValueError, \
                  "Only one of onlydirs and onlyfiles can be specified."
 764 
 765         comp_pattern, positions = self._compile_pattern(pattern)
 766 
 767         if self._id is not None and None in comp_pattern:
 768             on_path = set([self._id(self.stat())])
 769         else:
 770             on_path = None
 771 
 772         for x in self._glob(self, comp_pattern, topdown, onlydirs, onlyfiles,
 773                             positions, on_path, self.__class__.stat):
 774             yield x
 775 
 776     def lglob(self, pattern='*', topdown=True, onlydirs=False, onlyfiles=False):
 777         """ Return an iterator over all files in self matching pattern.
 778 
 779         pattern should be a relative path, which may include wildcards.
 780         In addition to the regular shell wildcards, you can use '**', which
 781         matches any number of directories, including 0.
 782 
 783         If topdown is True (the default), a directory is yielded before its
 784         descendents. If it's False, a directory is yielded after its
 785         descendents.
 786 
 787         If onlydirs is True, only directories will be yielded. If onlyfiles
 788         is True, only regular files will be yielded.
 789 
 790         This method treats symbolic links as special files - they won't be
 791         followed, and they will be yielded even if they're broken.
 792         """
 793 
 794         if onlydirs and onlyfiles:
 795             raise ValueError, \
                  "Only one of onlydirs and onlyfiles can be specified."
 796 
 797         comp_pattern, positions = self._compile_pattern(pattern)
 798 
 799         for x in self._glob(self, comp_pattern, topdown, onlydirs, onlyfiles,
 800                             positions, None, self.__class__.lstat):
 801             yield x
 802 
 803 
 804 class PosixPath(BasePath):
 805     """ Represents POSIX paths. """
 806 
 807     class _PosixRoot(BasePath._BaseRoot):
 808         """ Represents the filesystem root (/).
 809         
 810         There's only one root on posix systems, so this is a singleton.
 811         """
 812         instance = None
 813         def __new__(cls):
 814             if cls.instance is None:
 815                 instance = object.__new__(cls)
 816                 cls.instance = instance
 817             return cls.instance
 818 
 819         def __str__(self):
 820             return '/'
 821 
 822         def __repr__(self):
 823             return 'path.ROOT'
 824 
 825         isabs = True
 826 
 827     _OSBaseRoot = _PosixRoot
 828 
 829     ROOT = _PosixRoot()
 830 
 831     # Public constants
 832     curdir = '.'
 833     pardir = '..'
 834 
 835     # Private constants
 836     _sep = '/'
 837     _altsep = None
 838 
 839     @classmethod
 840     def _parse_str(cls, pathstr):
 841         # get a path string and return an iterable over path elements.
 842         if pathstr.startswith('/'):
 843             if pathstr.startswith('//') and not pathstr.startswith('///'):
 844                 # Two initial slashes have application-specific meaning
 845                 # in POSIX, and it's not supported currently.
 846                 raise NotImplementedError, \
                      "Paths with two leading slashes aren't supported."
 847             yield cls.ROOT
 848         for element in pathstr.split('/'):
 849             if element == '' or element == cls.curdir:
 850                 continue
 851             # '..' aren't specially treated, since popping the last
 852             # element isn't correct if the last element was a symbolic
 853             # link.
 854             yield element
 855 
 856 
 857     # POSIX-specific methods
 858 
 859 
 860     # --- Info about the path
 861 
 862     def statvfs(self):
 863         """ Perform a statvfs() system call on this path. """
 864         return os.statvfs(str(self))
 865 
 866     def pathconf(self, name):
 867         return os.pathconf(str(self), name)
 868 
 869     def samefile(self, other):
 870         other = self.__class__(other)
 871         s1 = self.stat()
 872         s2 = other.stat()
 873         return s1.st_ino == s2.st_ino and \
               s1.st_dev == s2.st_dev
 874 
 875 
 876     # --- Modifying operations on files and directories
 877 
 878     def chown(self, uid=None, gid=None):
 879         if uid is None:
 880             uid = -1
 881         if gid is None:
 882             gid = -1
 883         return os.chown(str(self), uid, gid)
 884 
 885     def lchown(self, uid=None, gid=None):
 886         if uid is None:
 887             uid = -1
 888         if gid is None:
 889             gid = -1
 890         return os.lchown(str(self), uid, gid)
 891 
 892 
 893     # --- Links
 894 
 895     def link(self, newpath):
 896         """ Create a hard link at 'newpath', pointing to this file. """
 897         os.link(str(self), str(newpath))
 898 
 899     def writelink(self, src):
 900         """ Create a symbolic link at self, pointing to src.
 901 
 902         src may be any string. Note that if it's a relative path, it
 903         will be interpreted relative to self, not relative to the current
 904         working directory.
 905         """
 906         os.symlink(str(src), str(self))
 907 
 908     def readlink(self):
 909         """ Return the path to which this symbolic link points.
 910 
 911         The result is a string, which may be an absolute path, a
 912         relative path (which should be interpreted relative to self[:-1]),
 913         or any arbitrary string.
 914         """
 915         return os.readlink(str(self))
 916 
 917     def readlinkpath(self):
 918         """ Return the path to which this symbolic link points. """
 919         linkpath = self.__class__(self.readlink())
 920         if linkpath.isrel:
 921             return self + linkpath
 922         else:
 923             return linkpath
 924 
 925 
 926     # --- Extra
 927 
 928     def mkfifo(self, *args):
 929         return os.mkfifo(str(self), *args)
 930 
 931     def mknod(self, *args):
 932         return os.mknod(str(self), *args)
 933 
 934     def chroot(self):
 935         return os.chroot(str(self))
 936 
 937 
 938     # --- Globbing
 939 
 940     @staticmethod
 941     def _id(stat):
 942         return (stat.st_ino, stat.st_dev)
 943 
 944 
 945 class NTPath(BasePath):
 946     """ Represents paths on Windows operating systems. """
 947 
 948     class _NTBaseRoot(BasePath._BaseRoot):
 949         """ The base class of all Windows root classes. """
 950         pass
 951 
 952     _OSBaseRoot = _NTBaseRoot
 953 
 954     class _CurRootType(_NTBaseRoot):
 955         """ Represents the root of the current working drive.
 956         
 957         This class is a singleton. It represents the root of the current
 958         working drive - paths starting with '\'.
 959         """
 960         instance = None
 961         def __new__(cls):
 962             if cls.instance is None:
 963                 instance = object.__new__(cls)
 964                 cls.instance = instance
 965             return cls.instance
 966 
 967         def __str__(self):
 968             return '\\'
 969 
 970         def __repr__(self):
 971             return 'path.CURROOT'
 972 
 973         isabs = False
 974 
 975         def abspath(self):
 976             from nt import _getfullpathname
 977             return NTPath(_getfullpathname(str(self)))
 978 
 979     CURROOT = _CurRootType()
 980 
 981     class Drive(_NTBaseRoot):
 982         """ Represents the root of a specific drive. """
 983         def __init__(self, letter):
 984             # Drive letter is normalized - we don't lose any information
 985             if len(letter) != 1 or letter not in string.letters:
 986                 raise ValueError, 'Should get one letter'
 987             self._letter = letter.lower()
 988 
 989         @property
 990         def letter(self):
 991             # We use a property because we want the object to be immutable.
 992             return self._letter
 993 
 994         def __str__(self):
 995             return '%s:\\' % self.letter
 996 
 997         def __repr__(self):
 998             return 'path.Drive(%r)' % self.letter
 999 
1000         isabs = True
1001 
1002     class UnrootedDrive(_NTBaseRoot):
1003         """ Represents the current working directory on a specific drive. """
1004         def __init__(self, letter):
1005             # Drive letter is normalized - we don't lose any information
1006             if len(letter) != 1 or letter not in string.letters:
1007                 raise ValueError, 'Should get one letter'
1008             self._letter = letter.lower()
1009 
1010         @property
1011         def letter(self):
1012             # We use a property because we want the object to be immutable.
1013             return self._letter
1014 
1015         def __str__(self):
1016             return '%s:' % self.letter
1017 
1018         def __repr__(self):
1019             return 'path.UnrootedDrive(%r)' % self.letter
1020 
1021         isabs = False
1022 
1023         def abspath(self):
1024             from nt import _getfullpathname
1025             return NTPath(_getfullpathname(str(self)))
1026 
1027     class UNCRoot(_NTBaseRoot):
1028         """ Represents a UNC mount point. """
1029         def __init__(self, host, mountpoint):
1030             # Host and mountpoint are normalized - we don't lose any information
1031             self._host = host.lower()
1032             self._mountpoint = mountpoint.lower()
1033 
1034         @property
1035         def host(self):
1036             # We use a property because we want the object to be immutable.
1037             return self._host
1038 
1039         @property
1040         def mountpoint(self):
1041             # We use a property because we want the object to be immutable.
1042             return self._mountpoint
1043 
1044         def __str__(self):
1045             return '\\\\%s\\%s\\' % (self.host, self.mountpoint)
1046 
1047         def __repr__(self):
1048             return 'path.UNCRoot(%r, %r)' % (self.host, self.mountpoint)
1049 
1050         isabs = True
1051 
1052 
1053     # Public constants
1054     curdir = '.'
1055     pardir = '..'
1056 
1057     # Private constants
1058     _sep = '\\'
1059     _altsep = '/'
1060 
1061     @staticmethod
1062     def normcasestr(string):
1063         """ Normalize the case of one path element.
1064         
1065         On Windows, this returns string.lower()
1066         """
1067         return string.lower()
1068 
1069     @classmethod
1070     def _parse_str(cls, pathstr):
1071         # get a path string and return an iterable over path elements.
1072 
1073         # First, replace all backslashes with slashes.
1074         # I know that it should have been the other way round, but I can't
1075         # stand all those escapes.
1076 
1077         pathstr = pathstr.replace('\\', '/')
1078 
1079         # Handle the root element
1080 
1081         if pathstr.startswith('/'):
1082             if pathstr.startswith('//'):
1083                 # UNC Path
1084                 if pathstr.startswith('///'):
1085                     raise ValueError, \
                          "Paths can't start with more than two slashes"
1086                 index = pathstr.find('/', 2)
1087                 if index == -1:
1088                     raise ValueError, \
                          "UNC host name should end with a slash"
1089                 index2 = index+1
1090                 while pathstr[index2:index2+1] == '/':
1091                     index2 += 1
1092                 if index2 == len(pathstr):
1093                     raise ValueError, \
                          "UNC mount point is empty"
1094                 index3 = pathstr.find('/', index2)
1095                 if index3 == -1:
1096                     index3 = len(pathstr)
1097                 yield cls.UNCRoot(pathstr[2:index], pathstr[index2:index3])
1098                 pathstr = pathstr[index3:]
1099             else:
1100                 # CURROOT
1101                 yield cls.CURROOT
1102         else:
1103             if pathstr[1:2] == ':':
1104                 if pathstr[2:3] == '/':
1105                     # Rooted drive
1106                     yield cls.Drive(pathstr[0])
1107                     pathstr = pathstr[3:]
1108                 else:
1109                     # Unrooted drive
1110                     yield cls.UnrootedDrive(pathstr[0])
1111                     pathstr = pathstr[2:]
1112 
1113         # Handle all other elements
1114 
1115         for element in pathstr.split('/'):
1116             if element == '' or element == cls.curdir:
1117                 continue
1118             # We don't treat pardir specially, since in the presence of
1119             # links there's nothing to do about them.
1120             # Windows doesn't have links, but why not keep path handling
1121             # similiar?
1122             yield element
1123 
1124 
1125     # NT-specific methods
1126 
1127     # --- Extra
1128 
1129     def startfile(self):
1130         return os.startfile(str(self))
1131 
1132 if os.name == 'posix':
1133     path = PosixPath
1134 elif os.name == 'nt':
1135     path = NTPath
1136 else:
1137     raise NotImplementedError, \
          "The path object is currently not implemented for OS %r" % os.name

EditText (last edited 2006-05-02 14:01:48 by NoamRaphael)