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