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
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
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
80
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
135 return hash(str(self))
136
137
138
139 _OSBaseRoot = None
140
141
142
143 curdir = None
144 pardir = None
145
146
147
148
149 _sep = None
150 _altsep = None
151
152 @staticmethod
153 def _parse_str(pathstr):
154
155
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
168
169
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
187
188
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
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
224 self._cached_str = None
225
226 def _build_str(self):
227
228
229
230
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
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
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
316
317
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
354
355
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
365 return dst
366
367
368
369
370
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
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
444
445
446
447
448
449
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
462
463
464
465
466
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
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
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568 _id = None
569
570 @staticmethod
571 def _match_element(comp_element, element):
572
573
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
607
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
616
617
618 try:
619 st = stat(newpth)
620 except OSError:
621 continue
622 newpositions = []
623 subfilenorm = cls.normcasestr(subfile)
624
625 if topdown:
626
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
641 break
642 if comp_element is None:
643 newpositions.append(pos)
644
645
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
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
683
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
731
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
739 break
740 newpositions.reverse()
741 positions = newpositions
742 if not positions:
743
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
838 curdir = '.'
839 pardir = '..'
840
841
842 _sep = '/'
843 _altsep = None
844
845 @classmethod
846 def _parse_str(cls, pathstr):
847
848 if pathstr.startswith('/'):
849 if pathstr.startswith('//') and not pathstr.startswith('///'):
850
851
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
859
860
861 yield element
862
863
864
865
866
867
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
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
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
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
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
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
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
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
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
1039 self._host = host.lower()
1040 self._mountpoint = mountpoint.lower()
1041
1042 @property
1043 def host(self):
1044
1045 return self._host
1046
1047 @property
1048 def mountpoint(self):
1049
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
1062 curdir = '.'
1063 pardir = '..'
1064
1065
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
1080
1081
1082
1083
1084
1085 pathstr = pathstr.replace('\\', '/')
1086
1087
1088
1089 if pathstr.startswith('/'):
1090 if pathstr.startswith('//'):
1091
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
1112 yield cls.CURROOT
1113 else:
1114 if pathstr[1:2] == ':':
1115 if pathstr[2:3] == '/':
1116
1117 yield cls.Drive(pathstr[0])
1118 pathstr = pathstr[3:]
1119 else:
1120
1121 yield cls.UnrootedDrive(pathstr[0])
1122 pathstr = pathstr[2:]
1123
1124
1125
1126 for element in pathstr.split('/'):
1127 if element == '' or element == cls.curdir:
1128 continue
1129
1130
1131
1132
1133 yield element
1134
1135
1136
1137
1138
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