• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import fnmatch
2import functools
3import io
4import ntpath
5import os
6import posixpath
7import re
8import sys
9from _collections_abc import Sequence
10from errno import EINVAL, ENOENT, ENOTDIR, EBADF, ELOOP
11from operator import attrgetter
12from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
13from urllib.parse import quote_from_bytes as urlquote_from_bytes
14
15
16supports_symlinks = True
17if os.name == 'nt':
18    import nt
19    if sys.getwindowsversion()[:2] >= (6, 0):
20        from nt import _getfinalpathname
21    else:
22        supports_symlinks = False
23        _getfinalpathname = None
24else:
25    nt = None
26
27
28__all__ = [
29    "PurePath", "PurePosixPath", "PureWindowsPath",
30    "Path", "PosixPath", "WindowsPath",
31    ]
32
33#
34# Internals
35#
36
37# EBADF - guard against macOS `stat` throwing EBADF
38_IGNORED_ERROS = (ENOENT, ENOTDIR, EBADF, ELOOP)
39
40_IGNORED_WINERRORS = (
41    21,  # ERROR_NOT_READY - drive exists but is not accessible
42    1921,  # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
43)
44
45def _ignore_error(exception):
46    return (getattr(exception, 'errno', None) in _IGNORED_ERROS or
47            getattr(exception, 'winerror', None) in _IGNORED_WINERRORS)
48
49
50def _is_wildcard_pattern(pat):
51    # Whether this pattern needs actual matching using fnmatch, or can
52    # be looked up directly as a file.
53    return "*" in pat or "?" in pat or "[" in pat
54
55
56class _Flavour(object):
57    """A flavour implements a particular (platform-specific) set of path
58    semantics."""
59
60    def __init__(self):
61        self.join = self.sep.join
62
63    def parse_parts(self, parts):
64        parsed = []
65        sep = self.sep
66        altsep = self.altsep
67        drv = root = ''
68        it = reversed(parts)
69        for part in it:
70            if not part:
71                continue
72            if altsep:
73                part = part.replace(altsep, sep)
74            drv, root, rel = self.splitroot(part)
75            if sep in rel:
76                for x in reversed(rel.split(sep)):
77                    if x and x != '.':
78                        parsed.append(sys.intern(x))
79            else:
80                if rel and rel != '.':
81                    parsed.append(sys.intern(rel))
82            if drv or root:
83                if not drv:
84                    # If no drive is present, try to find one in the previous
85                    # parts. This makes the result of parsing e.g.
86                    # ("C:", "/", "a") reasonably intuitive.
87                    for part in it:
88                        if not part:
89                            continue
90                        if altsep:
91                            part = part.replace(altsep, sep)
92                        drv = self.splitroot(part)[0]
93                        if drv:
94                            break
95                break
96        if drv or root:
97            parsed.append(drv + root)
98        parsed.reverse()
99        return drv, root, parsed
100
101    def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2):
102        """
103        Join the two paths represented by the respective
104        (drive, root, parts) tuples.  Return a new (drive, root, parts) tuple.
105        """
106        if root2:
107            if not drv2 and drv:
108                return drv, root2, [drv + root2] + parts2[1:]
109        elif drv2:
110            if drv2 == drv or self.casefold(drv2) == self.casefold(drv):
111                # Same drive => second path is relative to the first
112                return drv, root, parts + parts2[1:]
113        else:
114            # Second path is non-anchored (common case)
115            return drv, root, parts + parts2
116        return drv2, root2, parts2
117
118
119class _WindowsFlavour(_Flavour):
120    # Reference for Windows paths can be found at
121    # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx
122
123    sep = '\\'
124    altsep = '/'
125    has_drv = True
126    pathmod = ntpath
127
128    is_supported = (os.name == 'nt')
129
130    drive_letters = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
131    ext_namespace_prefix = '\\\\?\\'
132
133    reserved_names = (
134        {'CON', 'PRN', 'AUX', 'NUL'} |
135        {'COM%d' % i for i in range(1, 10)} |
136        {'LPT%d' % i for i in range(1, 10)}
137        )
138
139    # Interesting findings about extended paths:
140    # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported
141    #   but '\\?\c:/a' is not
142    # - extended paths are always absolute; "relative" extended paths will
143    #   fail.
144
145    def splitroot(self, part, sep=sep):
146        first = part[0:1]
147        second = part[1:2]
148        if (second == sep and first == sep):
149            # XXX extended paths should also disable the collapsing of "."
150            # components (according to MSDN docs).
151            prefix, part = self._split_extended_path(part)
152            first = part[0:1]
153            second = part[1:2]
154        else:
155            prefix = ''
156        third = part[2:3]
157        if (second == sep and first == sep and third != sep):
158            # is a UNC path:
159            # vvvvvvvvvvvvvvvvvvvvv root
160            # \\machine\mountpoint\directory\etc\...
161            #            directory ^^^^^^^^^^^^^^
162            index = part.find(sep, 2)
163            if index != -1:
164                index2 = part.find(sep, index + 1)
165                # a UNC path can't have two slashes in a row
166                # (after the initial two)
167                if index2 != index + 1:
168                    if index2 == -1:
169                        index2 = len(part)
170                    if prefix:
171                        return prefix + part[1:index2], sep, part[index2+1:]
172                    else:
173                        return part[:index2], sep, part[index2+1:]
174        drv = root = ''
175        if second == ':' and first in self.drive_letters:
176            drv = part[:2]
177            part = part[2:]
178            first = third
179        if first == sep:
180            root = first
181            part = part.lstrip(sep)
182        return prefix + drv, root, part
183
184    def casefold(self, s):
185        return s.lower()
186
187    def casefold_parts(self, parts):
188        return [p.lower() for p in parts]
189
190    def compile_pattern(self, pattern):
191        return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch
192
193    def resolve(self, path, strict=False):
194        s = str(path)
195        if not s:
196            return os.getcwd()
197        previous_s = None
198        if _getfinalpathname is not None:
199            if strict:
200                return self._ext_to_normal(_getfinalpathname(s))
201            else:
202                tail_parts = []  # End of the path after the first one not found
203                while True:
204                    try:
205                        s = self._ext_to_normal(_getfinalpathname(s))
206                    except FileNotFoundError:
207                        previous_s = s
208                        s, tail = os.path.split(s)
209                        tail_parts.append(tail)
210                        if previous_s == s:
211                            return path
212                    else:
213                        return os.path.join(s, *reversed(tail_parts))
214        # Means fallback on absolute
215        return None
216
217    def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
218        prefix = ''
219        if s.startswith(ext_prefix):
220            prefix = s[:4]
221            s = s[4:]
222            if s.startswith('UNC\\'):
223                prefix += s[:3]
224                s = '\\' + s[3:]
225        return prefix, s
226
227    def _ext_to_normal(self, s):
228        # Turn back an extended path into a normal DOS-like path
229        return self._split_extended_path(s)[1]
230
231    def is_reserved(self, parts):
232        # NOTE: the rules for reserved names seem somewhat complicated
233        # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
234        # We err on the side of caution and return True for paths which are
235        # not considered reserved by Windows.
236        if not parts:
237            return False
238        if parts[0].startswith('\\\\'):
239            # UNC paths are never reserved
240            return False
241        return parts[-1].partition('.')[0].upper() in self.reserved_names
242
243    def make_uri(self, path):
244        # Under Windows, file URIs use the UTF-8 encoding.
245        drive = path.drive
246        if len(drive) == 2 and drive[1] == ':':
247            # It's a path on a local drive => 'file:///c:/a/b'
248            rest = path.as_posix()[2:].lstrip('/')
249            return 'file:///%s/%s' % (
250                drive, urlquote_from_bytes(rest.encode('utf-8')))
251        else:
252            # It's a path on a network drive => 'file://host/share/a/b'
253            return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
254
255    def gethomedir(self, username):
256        if 'USERPROFILE' in os.environ:
257            userhome = os.environ['USERPROFILE']
258        elif 'HOMEPATH' in os.environ:
259            try:
260                drv = os.environ['HOMEDRIVE']
261            except KeyError:
262                drv = ''
263            userhome = drv + os.environ['HOMEPATH']
264        else:
265            raise RuntimeError("Can't determine home directory")
266
267        if username:
268            # Try to guess user home directory.  By default all users
269            # directories are located in the same place and are named by
270            # corresponding usernames.  If current user home directory points
271            # to nonstandard place, this guess is likely wrong.
272            if os.environ['USERNAME'] != username:
273                drv, root, parts = self.parse_parts((userhome,))
274                if parts[-1] != os.environ['USERNAME']:
275                    raise RuntimeError("Can't determine home directory "
276                                       "for %r" % username)
277                parts[-1] = username
278                if drv or root:
279                    userhome = drv + root + self.join(parts[1:])
280                else:
281                    userhome = self.join(parts)
282        return userhome
283
284class _PosixFlavour(_Flavour):
285    sep = '/'
286    altsep = ''
287    has_drv = False
288    pathmod = posixpath
289
290    is_supported = (os.name != 'nt')
291
292    def splitroot(self, part, sep=sep):
293        if part and part[0] == sep:
294            stripped_part = part.lstrip(sep)
295            # According to POSIX path resolution:
296            # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
297            # "A pathname that begins with two successive slashes may be
298            # interpreted in an implementation-defined manner, although more
299            # than two leading slashes shall be treated as a single slash".
300            if len(part) - len(stripped_part) == 2:
301                return '', sep * 2, stripped_part
302            else:
303                return '', sep, stripped_part
304        else:
305            return '', '', part
306
307    def casefold(self, s):
308        return s
309
310    def casefold_parts(self, parts):
311        return parts
312
313    def compile_pattern(self, pattern):
314        return re.compile(fnmatch.translate(pattern)).fullmatch
315
316    def resolve(self, path, strict=False):
317        sep = self.sep
318        accessor = path._accessor
319        seen = {}
320        def _resolve(path, rest):
321            if rest.startswith(sep):
322                path = ''
323
324            for name in rest.split(sep):
325                if not name or name == '.':
326                    # current dir
327                    continue
328                if name == '..':
329                    # parent dir
330                    path, _, _ = path.rpartition(sep)
331                    continue
332                newpath = path + sep + name
333                if newpath in seen:
334                    # Already seen this path
335                    path = seen[newpath]
336                    if path is not None:
337                        # use cached value
338                        continue
339                    # The symlink is not resolved, so we must have a symlink loop.
340                    raise RuntimeError("Symlink loop from %r" % newpath)
341                # Resolve the symbolic link
342                try:
343                    target = accessor.readlink(newpath)
344                except OSError as e:
345                    if e.errno != EINVAL and strict:
346                        raise
347                    # Not a symlink, or non-strict mode. We just leave the path
348                    # untouched.
349                    path = newpath
350                else:
351                    seen[newpath] = None # not resolved symlink
352                    path = _resolve(path, target)
353                    seen[newpath] = path # resolved symlink
354
355            return path
356        # NOTE: according to POSIX, getcwd() cannot contain path components
357        # which are symlinks.
358        base = '' if path.is_absolute() else os.getcwd()
359        return _resolve(base, str(path)) or sep
360
361    def is_reserved(self, parts):
362        return False
363
364    def make_uri(self, path):
365        # We represent the path using the local filesystem encoding,
366        # for portability to other applications.
367        bpath = bytes(path)
368        return 'file://' + urlquote_from_bytes(bpath)
369
370    def gethomedir(self, username):
371        if not username:
372            try:
373                return os.environ['HOME']
374            except KeyError:
375                import pwd
376                return pwd.getpwuid(os.getuid()).pw_dir
377        else:
378            import pwd
379            try:
380                return pwd.getpwnam(username).pw_dir
381            except KeyError:
382                raise RuntimeError("Can't determine home directory "
383                                   "for %r" % username)
384
385
386_windows_flavour = _WindowsFlavour()
387_posix_flavour = _PosixFlavour()
388
389
390class _Accessor:
391    """An accessor implements a particular (system-specific or not) way of
392    accessing paths on the filesystem."""
393
394
395class _NormalAccessor(_Accessor):
396
397    stat = os.stat
398
399    lstat = os.lstat
400
401    open = os.open
402
403    listdir = os.listdir
404
405    scandir = os.scandir
406
407    chmod = os.chmod
408
409    if hasattr(os, "lchmod"):
410        lchmod = os.lchmod
411    else:
412        def lchmod(self, pathobj, mode):
413            raise NotImplementedError("lchmod() not available on this system")
414
415    mkdir = os.mkdir
416
417    unlink = os.unlink
418
419    if hasattr(os, "link"):
420        link_to = os.link
421    else:
422        @staticmethod
423        def link_to(self, target):
424            raise NotImplementedError("os.link() not available on this system")
425
426    rmdir = os.rmdir
427
428    rename = os.rename
429
430    replace = os.replace
431
432    if nt:
433        if supports_symlinks:
434            symlink = os.symlink
435        else:
436            def symlink(a, b, target_is_directory):
437                raise NotImplementedError("symlink() not available on this system")
438    else:
439        # Under POSIX, os.symlink() takes two args
440        @staticmethod
441        def symlink(a, b, target_is_directory):
442            return os.symlink(a, b)
443
444    utime = os.utime
445
446    # Helper for resolve()
447    def readlink(self, path):
448        return os.readlink(path)
449
450
451_normal_accessor = _NormalAccessor()
452
453
454#
455# Globbing helpers
456#
457
458def _make_selector(pattern_parts, flavour):
459    pat = pattern_parts[0]
460    child_parts = pattern_parts[1:]
461    if pat == '**':
462        cls = _RecursiveWildcardSelector
463    elif '**' in pat:
464        raise ValueError("Invalid pattern: '**' can only be an entire path component")
465    elif _is_wildcard_pattern(pat):
466        cls = _WildcardSelector
467    else:
468        cls = _PreciseSelector
469    return cls(pat, child_parts, flavour)
470
471if hasattr(functools, "lru_cache"):
472    _make_selector = functools.lru_cache()(_make_selector)
473
474
475class _Selector:
476    """A selector matches a specific glob pattern part against the children
477    of a given path."""
478
479    def __init__(self, child_parts, flavour):
480        self.child_parts = child_parts
481        if child_parts:
482            self.successor = _make_selector(child_parts, flavour)
483            self.dironly = True
484        else:
485            self.successor = _TerminatingSelector()
486            self.dironly = False
487
488    def select_from(self, parent_path):
489        """Iterate over all child paths of `parent_path` matched by this
490        selector.  This can contain parent_path itself."""
491        path_cls = type(parent_path)
492        is_dir = path_cls.is_dir
493        exists = path_cls.exists
494        scandir = parent_path._accessor.scandir
495        if not is_dir(parent_path):
496            return iter([])
497        return self._select_from(parent_path, is_dir, exists, scandir)
498
499
500class _TerminatingSelector:
501
502    def _select_from(self, parent_path, is_dir, exists, scandir):
503        yield parent_path
504
505
506class _PreciseSelector(_Selector):
507
508    def __init__(self, name, child_parts, flavour):
509        self.name = name
510        _Selector.__init__(self, child_parts, flavour)
511
512    def _select_from(self, parent_path, is_dir, exists, scandir):
513        try:
514            path = parent_path._make_child_relpath(self.name)
515            if (is_dir if self.dironly else exists)(path):
516                for p in self.successor._select_from(path, is_dir, exists, scandir):
517                    yield p
518        except PermissionError:
519            return
520
521
522class _WildcardSelector(_Selector):
523
524    def __init__(self, pat, child_parts, flavour):
525        self.match = flavour.compile_pattern(pat)
526        _Selector.__init__(self, child_parts, flavour)
527
528    def _select_from(self, parent_path, is_dir, exists, scandir):
529        try:
530            with scandir(parent_path) as scandir_it:
531                entries = list(scandir_it)
532            for entry in entries:
533                if self.dironly:
534                    try:
535                        # "entry.is_dir()" can raise PermissionError
536                        # in some cases (see bpo-38894), which is not
537                        # among the errors ignored by _ignore_error()
538                        if not entry.is_dir():
539                            continue
540                    except OSError as e:
541                        if not _ignore_error(e):
542                            raise
543                        continue
544                name = entry.name
545                if self.match(name):
546                    path = parent_path._make_child_relpath(name)
547                    for p in self.successor._select_from(path, is_dir, exists, scandir):
548                        yield p
549        except PermissionError:
550            return
551
552
553class _RecursiveWildcardSelector(_Selector):
554
555    def __init__(self, pat, child_parts, flavour):
556        _Selector.__init__(self, child_parts, flavour)
557
558    def _iterate_directories(self, parent_path, is_dir, scandir):
559        yield parent_path
560        try:
561            with scandir(parent_path) as scandir_it:
562                entries = list(scandir_it)
563            for entry in entries:
564                entry_is_dir = False
565                try:
566                    entry_is_dir = entry.is_dir()
567                except OSError as e:
568                    if not _ignore_error(e):
569                        raise
570                if entry_is_dir and not entry.is_symlink():
571                    path = parent_path._make_child_relpath(entry.name)
572                    for p in self._iterate_directories(path, is_dir, scandir):
573                        yield p
574        except PermissionError:
575            return
576
577    def _select_from(self, parent_path, is_dir, exists, scandir):
578        try:
579            yielded = set()
580            try:
581                successor_select = self.successor._select_from
582                for starting_point in self._iterate_directories(parent_path, is_dir, scandir):
583                    for p in successor_select(starting_point, is_dir, exists, scandir):
584                        if p not in yielded:
585                            yield p
586                            yielded.add(p)
587            finally:
588                yielded.clear()
589        except PermissionError:
590            return
591
592
593#
594# Public API
595#
596
597class _PathParents(Sequence):
598    """This object provides sequence-like access to the logical ancestors
599    of a path.  Don't try to construct it yourself."""
600    __slots__ = ('_pathcls', '_drv', '_root', '_parts')
601
602    def __init__(self, path):
603        # We don't store the instance to avoid reference cycles
604        self._pathcls = type(path)
605        self._drv = path._drv
606        self._root = path._root
607        self._parts = path._parts
608
609    def __len__(self):
610        if self._drv or self._root:
611            return len(self._parts) - 1
612        else:
613            return len(self._parts)
614
615    def __getitem__(self, idx):
616        if idx < 0 or idx >= len(self):
617            raise IndexError(idx)
618        return self._pathcls._from_parsed_parts(self._drv, self._root,
619                                                self._parts[:-idx - 1])
620
621    def __repr__(self):
622        return "<{}.parents>".format(self._pathcls.__name__)
623
624
625class PurePath(object):
626    """Base class for manipulating paths without I/O.
627
628    PurePath represents a filesystem path and offers operations which
629    don't imply any actual filesystem I/O.  Depending on your system,
630    instantiating a PurePath will return either a PurePosixPath or a
631    PureWindowsPath object.  You can also instantiate either of these classes
632    directly, regardless of your system.
633    """
634    __slots__ = (
635        '_drv', '_root', '_parts',
636        '_str', '_hash', '_pparts', '_cached_cparts',
637    )
638
639    def __new__(cls, *args):
640        """Construct a PurePath from one or several strings and or existing
641        PurePath objects.  The strings and path objects are combined so as
642        to yield a canonicalized path, which is incorporated into the
643        new PurePath object.
644        """
645        if cls is PurePath:
646            cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
647        return cls._from_parts(args)
648
649    def __reduce__(self):
650        # Using the parts tuple helps share interned path parts
651        # when pickling related paths.
652        return (self.__class__, tuple(self._parts))
653
654    @classmethod
655    def _parse_args(cls, args):
656        # This is useful when you don't want to create an instance, just
657        # canonicalize some constructor arguments.
658        parts = []
659        for a in args:
660            if isinstance(a, PurePath):
661                parts += a._parts
662            else:
663                a = os.fspath(a)
664                if isinstance(a, str):
665                    # Force-cast str subclasses to str (issue #21127)
666                    parts.append(str(a))
667                else:
668                    raise TypeError(
669                        "argument should be a str object or an os.PathLike "
670                        "object returning str, not %r"
671                        % type(a))
672        return cls._flavour.parse_parts(parts)
673
674    @classmethod
675    def _from_parts(cls, args, init=True):
676        # We need to call _parse_args on the instance, so as to get the
677        # right flavour.
678        self = object.__new__(cls)
679        drv, root, parts = self._parse_args(args)
680        self._drv = drv
681        self._root = root
682        self._parts = parts
683        if init:
684            self._init()
685        return self
686
687    @classmethod
688    def _from_parsed_parts(cls, drv, root, parts, init=True):
689        self = object.__new__(cls)
690        self._drv = drv
691        self._root = root
692        self._parts = parts
693        if init:
694            self._init()
695        return self
696
697    @classmethod
698    def _format_parsed_parts(cls, drv, root, parts):
699        if drv or root:
700            return drv + root + cls._flavour.join(parts[1:])
701        else:
702            return cls._flavour.join(parts)
703
704    def _init(self):
705        # Overridden in concrete Path
706        pass
707
708    def _make_child(self, args):
709        drv, root, parts = self._parse_args(args)
710        drv, root, parts = self._flavour.join_parsed_parts(
711            self._drv, self._root, self._parts, drv, root, parts)
712        return self._from_parsed_parts(drv, root, parts)
713
714    def __str__(self):
715        """Return the string representation of the path, suitable for
716        passing to system calls."""
717        try:
718            return self._str
719        except AttributeError:
720            self._str = self._format_parsed_parts(self._drv, self._root,
721                                                  self._parts) or '.'
722            return self._str
723
724    def __fspath__(self):
725        return str(self)
726
727    def as_posix(self):
728        """Return the string representation of the path with forward (/)
729        slashes."""
730        f = self._flavour
731        return str(self).replace(f.sep, '/')
732
733    def __bytes__(self):
734        """Return the bytes representation of the path.  This is only
735        recommended to use under Unix."""
736        return os.fsencode(self)
737
738    def __repr__(self):
739        return "{}({!r})".format(self.__class__.__name__, self.as_posix())
740
741    def as_uri(self):
742        """Return the path as a 'file' URI."""
743        if not self.is_absolute():
744            raise ValueError("relative path can't be expressed as a file URI")
745        return self._flavour.make_uri(self)
746
747    @property
748    def _cparts(self):
749        # Cached casefolded parts, for hashing and comparison
750        try:
751            return self._cached_cparts
752        except AttributeError:
753            self._cached_cparts = self._flavour.casefold_parts(self._parts)
754            return self._cached_cparts
755
756    def __eq__(self, other):
757        if not isinstance(other, PurePath):
758            return NotImplemented
759        return self._cparts == other._cparts and self._flavour is other._flavour
760
761    def __hash__(self):
762        try:
763            return self._hash
764        except AttributeError:
765            self._hash = hash(tuple(self._cparts))
766            return self._hash
767
768    def __lt__(self, other):
769        if not isinstance(other, PurePath) or self._flavour is not other._flavour:
770            return NotImplemented
771        return self._cparts < other._cparts
772
773    def __le__(self, other):
774        if not isinstance(other, PurePath) or self._flavour is not other._flavour:
775            return NotImplemented
776        return self._cparts <= other._cparts
777
778    def __gt__(self, other):
779        if not isinstance(other, PurePath) or self._flavour is not other._flavour:
780            return NotImplemented
781        return self._cparts > other._cparts
782
783    def __ge__(self, other):
784        if not isinstance(other, PurePath) or self._flavour is not other._flavour:
785            return NotImplemented
786        return self._cparts >= other._cparts
787
788    drive = property(attrgetter('_drv'),
789                     doc="""The drive prefix (letter or UNC path), if any.""")
790
791    root = property(attrgetter('_root'),
792                    doc="""The root of the path, if any.""")
793
794    @property
795    def anchor(self):
796        """The concatenation of the drive and root, or ''."""
797        anchor = self._drv + self._root
798        return anchor
799
800    @property
801    def name(self):
802        """The final path component, if any."""
803        parts = self._parts
804        if len(parts) == (1 if (self._drv or self._root) else 0):
805            return ''
806        return parts[-1]
807
808    @property
809    def suffix(self):
810        """
811        The final component's last suffix, if any.
812
813        This includes the leading period. For example: '.txt'
814        """
815        name = self.name
816        i = name.rfind('.')
817        if 0 < i < len(name) - 1:
818            return name[i:]
819        else:
820            return ''
821
822    @property
823    def suffixes(self):
824        """
825        A list of the final component's suffixes, if any.
826
827        These include the leading periods. For example: ['.tar', '.gz']
828        """
829        name = self.name
830        if name.endswith('.'):
831            return []
832        name = name.lstrip('.')
833        return ['.' + suffix for suffix in name.split('.')[1:]]
834
835    @property
836    def stem(self):
837        """The final path component, minus its last suffix."""
838        name = self.name
839        i = name.rfind('.')
840        if 0 < i < len(name) - 1:
841            return name[:i]
842        else:
843            return name
844
845    def with_name(self, name):
846        """Return a new path with the file name changed."""
847        if not self.name:
848            raise ValueError("%r has an empty name" % (self,))
849        drv, root, parts = self._flavour.parse_parts((name,))
850        if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep]
851            or drv or root or len(parts) != 1):
852            raise ValueError("Invalid name %r" % (name))
853        return self._from_parsed_parts(self._drv, self._root,
854                                       self._parts[:-1] + [name])
855
856    def with_suffix(self, suffix):
857        """Return a new path with the file suffix changed.  If the path
858        has no suffix, add given suffix.  If the given suffix is an empty
859        string, remove the suffix from the path.
860        """
861        f = self._flavour
862        if f.sep in suffix or f.altsep and f.altsep in suffix:
863            raise ValueError("Invalid suffix %r" % (suffix,))
864        if suffix and not suffix.startswith('.') or suffix == '.':
865            raise ValueError("Invalid suffix %r" % (suffix))
866        name = self.name
867        if not name:
868            raise ValueError("%r has an empty name" % (self,))
869        old_suffix = self.suffix
870        if not old_suffix:
871            name = name + suffix
872        else:
873            name = name[:-len(old_suffix)] + suffix
874        return self._from_parsed_parts(self._drv, self._root,
875                                       self._parts[:-1] + [name])
876
877    def relative_to(self, *other):
878        """Return the relative path to another path identified by the passed
879        arguments.  If the operation is not possible (because this is not
880        a subpath of the other path), raise ValueError.
881        """
882        # For the purpose of this method, drive and root are considered
883        # separate parts, i.e.:
884        #   Path('c:/').relative_to('c:')  gives Path('/')
885        #   Path('c:/').relative_to('/')   raise ValueError
886        if not other:
887            raise TypeError("need at least one argument")
888        parts = self._parts
889        drv = self._drv
890        root = self._root
891        if root:
892            abs_parts = [drv, root] + parts[1:]
893        else:
894            abs_parts = parts
895        to_drv, to_root, to_parts = self._parse_args(other)
896        if to_root:
897            to_abs_parts = [to_drv, to_root] + to_parts[1:]
898        else:
899            to_abs_parts = to_parts
900        n = len(to_abs_parts)
901        cf = self._flavour.casefold_parts
902        if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
903            formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
904            raise ValueError("{!r} does not start with {!r}"
905                             .format(str(self), str(formatted)))
906        return self._from_parsed_parts('', root if n == 1 else '',
907                                       abs_parts[n:])
908
909    @property
910    def parts(self):
911        """An object providing sequence-like access to the
912        components in the filesystem path."""
913        # We cache the tuple to avoid building a new one each time .parts
914        # is accessed.  XXX is this necessary?
915        try:
916            return self._pparts
917        except AttributeError:
918            self._pparts = tuple(self._parts)
919            return self._pparts
920
921    def joinpath(self, *args):
922        """Combine this path with one or several arguments, and return a
923        new path representing either a subpath (if all arguments are relative
924        paths) or a totally different path (if one of the arguments is
925        anchored).
926        """
927        return self._make_child(args)
928
929    def __truediv__(self, key):
930        try:
931            return self._make_child((key,))
932        except TypeError:
933            return NotImplemented
934
935    def __rtruediv__(self, key):
936        try:
937            return self._from_parts([key] + self._parts)
938        except TypeError:
939            return NotImplemented
940
941    @property
942    def parent(self):
943        """The logical parent of the path."""
944        drv = self._drv
945        root = self._root
946        parts = self._parts
947        if len(parts) == 1 and (drv or root):
948            return self
949        return self._from_parsed_parts(drv, root, parts[:-1])
950
951    @property
952    def parents(self):
953        """A sequence of this path's logical parents."""
954        return _PathParents(self)
955
956    def is_absolute(self):
957        """True if the path is absolute (has both a root and, if applicable,
958        a drive)."""
959        if not self._root:
960            return False
961        return not self._flavour.has_drv or bool(self._drv)
962
963    def is_reserved(self):
964        """Return True if the path contains one of the special names reserved
965        by the system, if any."""
966        return self._flavour.is_reserved(self._parts)
967
968    def match(self, path_pattern):
969        """
970        Return True if this path matches the given pattern.
971        """
972        cf = self._flavour.casefold
973        path_pattern = cf(path_pattern)
974        drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
975        if not pat_parts:
976            raise ValueError("empty pattern")
977        if drv and drv != cf(self._drv):
978            return False
979        if root and root != cf(self._root):
980            return False
981        parts = self._cparts
982        if drv or root:
983            if len(pat_parts) != len(parts):
984                return False
985            pat_parts = pat_parts[1:]
986        elif len(pat_parts) > len(parts):
987            return False
988        for part, pat in zip(reversed(parts), reversed(pat_parts)):
989            if not fnmatch.fnmatchcase(part, pat):
990                return False
991        return True
992
993# Can't subclass os.PathLike from PurePath and keep the constructor
994# optimizations in PurePath._parse_args().
995os.PathLike.register(PurePath)
996
997
998class PurePosixPath(PurePath):
999    """PurePath subclass for non-Windows systems.
1000
1001    On a POSIX system, instantiating a PurePath should return this object.
1002    However, you can also instantiate it directly on any system.
1003    """
1004    _flavour = _posix_flavour
1005    __slots__ = ()
1006
1007
1008class PureWindowsPath(PurePath):
1009    """PurePath subclass for Windows systems.
1010
1011    On a Windows system, instantiating a PurePath should return this object.
1012    However, you can also instantiate it directly on any system.
1013    """
1014    _flavour = _windows_flavour
1015    __slots__ = ()
1016
1017
1018# Filesystem-accessing classes
1019
1020
1021class Path(PurePath):
1022    """PurePath subclass that can make system calls.
1023
1024    Path represents a filesystem path but unlike PurePath, also offers
1025    methods to do system calls on path objects. Depending on your system,
1026    instantiating a Path will return either a PosixPath or a WindowsPath
1027    object. You can also instantiate a PosixPath or WindowsPath directly,
1028    but cannot instantiate a WindowsPath on a POSIX system or vice versa.
1029    """
1030    __slots__ = (
1031        '_accessor',
1032        '_closed',
1033    )
1034
1035    def __new__(cls, *args, **kwargs):
1036        if cls is Path:
1037            cls = WindowsPath if os.name == 'nt' else PosixPath
1038        self = cls._from_parts(args, init=False)
1039        if not self._flavour.is_supported:
1040            raise NotImplementedError("cannot instantiate %r on your system"
1041                                      % (cls.__name__,))
1042        self._init()
1043        return self
1044
1045    def _init(self,
1046              # Private non-constructor arguments
1047              template=None,
1048              ):
1049        self._closed = False
1050        if template is not None:
1051            self._accessor = template._accessor
1052        else:
1053            self._accessor = _normal_accessor
1054
1055    def _make_child_relpath(self, part):
1056        # This is an optimization used for dir walking.  `part` must be
1057        # a single part relative to this path.
1058        parts = self._parts + [part]
1059        return self._from_parsed_parts(self._drv, self._root, parts)
1060
1061    def __enter__(self):
1062        if self._closed:
1063            self._raise_closed()
1064        return self
1065
1066    def __exit__(self, t, v, tb):
1067        self._closed = True
1068
1069    def _raise_closed(self):
1070        raise ValueError("I/O operation on closed path")
1071
1072    def _opener(self, name, flags, mode=0o666):
1073        # A stub for the opener argument to built-in open()
1074        return self._accessor.open(self, flags, mode)
1075
1076    def _raw_open(self, flags, mode=0o777):
1077        """
1078        Open the file pointed by this path and return a file descriptor,
1079        as os.open() does.
1080        """
1081        if self._closed:
1082            self._raise_closed()
1083        return self._accessor.open(self, flags, mode)
1084
1085    # Public API
1086
1087    @classmethod
1088    def cwd(cls):
1089        """Return a new path pointing to the current working directory
1090        (as returned by os.getcwd()).
1091        """
1092        return cls(os.getcwd())
1093
1094    @classmethod
1095    def home(cls):
1096        """Return a new path pointing to the user's home directory (as
1097        returned by os.path.expanduser('~')).
1098        """
1099        return cls(cls()._flavour.gethomedir(None))
1100
1101    def samefile(self, other_path):
1102        """Return whether other_path is the same or not as this file
1103        (as returned by os.path.samefile()).
1104        """
1105        st = self.stat()
1106        try:
1107            other_st = other_path.stat()
1108        except AttributeError:
1109            other_st = os.stat(other_path)
1110        return os.path.samestat(st, other_st)
1111
1112    def iterdir(self):
1113        """Iterate over the files in this directory.  Does not yield any
1114        result for the special paths '.' and '..'.
1115        """
1116        if self._closed:
1117            self._raise_closed()
1118        for name in self._accessor.listdir(self):
1119            if name in {'.', '..'}:
1120                # Yielding a path object for these makes little sense
1121                continue
1122            yield self._make_child_relpath(name)
1123            if self._closed:
1124                self._raise_closed()
1125
1126    def glob(self, pattern):
1127        """Iterate over this subtree and yield all existing files (of any
1128        kind, including directories) matching the given relative pattern.
1129        """
1130        if not pattern:
1131            raise ValueError("Unacceptable pattern: {!r}".format(pattern))
1132        drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1133        if drv or root:
1134            raise NotImplementedError("Non-relative patterns are unsupported")
1135        selector = _make_selector(tuple(pattern_parts), self._flavour)
1136        for p in selector.select_from(self):
1137            yield p
1138
1139    def rglob(self, pattern):
1140        """Recursively yield all existing files (of any kind, including
1141        directories) matching the given relative pattern, anywhere in
1142        this subtree.
1143        """
1144        drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1145        if drv or root:
1146            raise NotImplementedError("Non-relative patterns are unsupported")
1147        selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour)
1148        for p in selector.select_from(self):
1149            yield p
1150
1151    def absolute(self):
1152        """Return an absolute version of this path.  This function works
1153        even if the path doesn't point to anything.
1154
1155        No normalization is done, i.e. all '.' and '..' will be kept along.
1156        Use resolve() to get the canonical path to a file.
1157        """
1158        # XXX untested yet!
1159        if self._closed:
1160            self._raise_closed()
1161        if self.is_absolute():
1162            return self
1163        # FIXME this must defer to the specific flavour (and, under Windows,
1164        # use nt._getfullpathname())
1165        obj = self._from_parts([os.getcwd()] + self._parts, init=False)
1166        obj._init(template=self)
1167        return obj
1168
1169    def resolve(self, strict=False):
1170        """
1171        Make the path absolute, resolving all symlinks on the way and also
1172        normalizing it (for example turning slashes into backslashes under
1173        Windows).
1174        """
1175        if self._closed:
1176            self._raise_closed()
1177        s = self._flavour.resolve(self, strict=strict)
1178        if s is None:
1179            # No symlink resolution => for consistency, raise an error if
1180            # the path doesn't exist or is forbidden
1181            self.stat()
1182            s = str(self.absolute())
1183        # Now we have no symlinks in the path, it's safe to normalize it.
1184        normed = self._flavour.pathmod.normpath(s)
1185        obj = self._from_parts((normed,), init=False)
1186        obj._init(template=self)
1187        return obj
1188
1189    def stat(self):
1190        """
1191        Return the result of the stat() system call on this path, like
1192        os.stat() does.
1193        """
1194        return self._accessor.stat(self)
1195
1196    def owner(self):
1197        """
1198        Return the login name of the file owner.
1199        """
1200        import pwd
1201        return pwd.getpwuid(self.stat().st_uid).pw_name
1202
1203    def group(self):
1204        """
1205        Return the group name of the file gid.
1206        """
1207        import grp
1208        return grp.getgrgid(self.stat().st_gid).gr_name
1209
1210    def open(self, mode='r', buffering=-1, encoding=None,
1211             errors=None, newline=None):
1212        """
1213        Open the file pointed by this path and return a file object, as
1214        the built-in open() function does.
1215        """
1216        if self._closed:
1217            self._raise_closed()
1218        return io.open(self, mode, buffering, encoding, errors, newline,
1219                       opener=self._opener)
1220
1221    def read_bytes(self):
1222        """
1223        Open the file in bytes mode, read it, and close the file.
1224        """
1225        with self.open(mode='rb') as f:
1226            return f.read()
1227
1228    def read_text(self, encoding=None, errors=None):
1229        """
1230        Open the file in text mode, read it, and close the file.
1231        """
1232        with self.open(mode='r', encoding=encoding, errors=errors) as f:
1233            return f.read()
1234
1235    def write_bytes(self, data):
1236        """
1237        Open the file in bytes mode, write to it, and close the file.
1238        """
1239        # type-check for the buffer interface before truncating the file
1240        view = memoryview(data)
1241        with self.open(mode='wb') as f:
1242            return f.write(view)
1243
1244    def write_text(self, data, encoding=None, errors=None):
1245        """
1246        Open the file in text mode, write to it, and close the file.
1247        """
1248        if not isinstance(data, str):
1249            raise TypeError('data must be str, not %s' %
1250                            data.__class__.__name__)
1251        with self.open(mode='w', encoding=encoding, errors=errors) as f:
1252            return f.write(data)
1253
1254    def touch(self, mode=0o666, exist_ok=True):
1255        """
1256        Create this file with the given access mode, if it doesn't exist.
1257        """
1258        if self._closed:
1259            self._raise_closed()
1260        if exist_ok:
1261            # First try to bump modification time
1262            # Implementation note: GNU touch uses the UTIME_NOW option of
1263            # the utimensat() / futimens() functions.
1264            try:
1265                self._accessor.utime(self, None)
1266            except OSError:
1267                # Avoid exception chaining
1268                pass
1269            else:
1270                return
1271        flags = os.O_CREAT | os.O_WRONLY
1272        if not exist_ok:
1273            flags |= os.O_EXCL
1274        fd = self._raw_open(flags, mode)
1275        os.close(fd)
1276
1277    def mkdir(self, mode=0o777, parents=False, exist_ok=False):
1278        """
1279        Create a new directory at this given path.
1280        """
1281        if self._closed:
1282            self._raise_closed()
1283        try:
1284            self._accessor.mkdir(self, mode)
1285        except FileNotFoundError:
1286            if not parents or self.parent == self:
1287                raise
1288            self.parent.mkdir(parents=True, exist_ok=True)
1289            self.mkdir(mode, parents=False, exist_ok=exist_ok)
1290        except OSError:
1291            # Cannot rely on checking for EEXIST, since the operating system
1292            # could give priority to other errors like EACCES or EROFS
1293            if not exist_ok or not self.is_dir():
1294                raise
1295
1296    def chmod(self, mode):
1297        """
1298        Change the permissions of the path, like os.chmod().
1299        """
1300        if self._closed:
1301            self._raise_closed()
1302        self._accessor.chmod(self, mode)
1303
1304    def lchmod(self, mode):
1305        """
1306        Like chmod(), except if the path points to a symlink, the symlink's
1307        permissions are changed, rather than its target's.
1308        """
1309        if self._closed:
1310            self._raise_closed()
1311        self._accessor.lchmod(self, mode)
1312
1313    def unlink(self, missing_ok=False):
1314        """
1315        Remove this file or link.
1316        If the path is a directory, use rmdir() instead.
1317        """
1318        if self._closed:
1319            self._raise_closed()
1320        try:
1321            self._accessor.unlink(self)
1322        except FileNotFoundError:
1323            if not missing_ok:
1324                raise
1325
1326    def rmdir(self):
1327        """
1328        Remove this directory.  The directory must be empty.
1329        """
1330        if self._closed:
1331            self._raise_closed()
1332        self._accessor.rmdir(self)
1333
1334    def lstat(self):
1335        """
1336        Like stat(), except if the path points to a symlink, the symlink's
1337        status information is returned, rather than its target's.
1338        """
1339        if self._closed:
1340            self._raise_closed()
1341        return self._accessor.lstat(self)
1342
1343    def link_to(self, target):
1344        """
1345        Create a hard link pointing to a path named target.
1346        """
1347        if self._closed:
1348            self._raise_closed()
1349        self._accessor.link_to(self, target)
1350
1351    def rename(self, target):
1352        """
1353        Rename this path to the given path,
1354        and return a new Path instance pointing to the given path.
1355        """
1356        if self._closed:
1357            self._raise_closed()
1358        self._accessor.rename(self, target)
1359        return self.__class__(target)
1360
1361    def replace(self, target):
1362        """
1363        Rename this path to the given path, clobbering the existing
1364        destination if it exists, and return a new Path instance
1365        pointing to the given path.
1366        """
1367        if self._closed:
1368            self._raise_closed()
1369        self._accessor.replace(self, target)
1370        return self.__class__(target)
1371
1372    def symlink_to(self, target, target_is_directory=False):
1373        """
1374        Make this path a symlink pointing to the given path.
1375        Note the order of arguments (self, target) is the reverse of os.symlink's.
1376        """
1377        if self._closed:
1378            self._raise_closed()
1379        self._accessor.symlink(target, self, target_is_directory)
1380
1381    # Convenience functions for querying the stat results
1382
1383    def exists(self):
1384        """
1385        Whether this path exists.
1386        """
1387        try:
1388            self.stat()
1389        except OSError as e:
1390            if not _ignore_error(e):
1391                raise
1392            return False
1393        except ValueError:
1394            # Non-encodable path
1395            return False
1396        return True
1397
1398    def is_dir(self):
1399        """
1400        Whether this path is a directory.
1401        """
1402        try:
1403            return S_ISDIR(self.stat().st_mode)
1404        except OSError as e:
1405            if not _ignore_error(e):
1406                raise
1407            # Path doesn't exist or is a broken symlink
1408            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1409            return False
1410        except ValueError:
1411            # Non-encodable path
1412            return False
1413
1414    def is_file(self):
1415        """
1416        Whether this path is a regular file (also True for symlinks pointing
1417        to regular files).
1418        """
1419        try:
1420            return S_ISREG(self.stat().st_mode)
1421        except OSError as e:
1422            if not _ignore_error(e):
1423                raise
1424            # Path doesn't exist or is a broken symlink
1425            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1426            return False
1427        except ValueError:
1428            # Non-encodable path
1429            return False
1430
1431    def is_mount(self):
1432        """
1433        Check if this path is a POSIX mount point
1434        """
1435        # Need to exist and be a dir
1436        if not self.exists() or not self.is_dir():
1437            return False
1438
1439        parent = Path(self.parent)
1440        try:
1441            parent_dev = parent.stat().st_dev
1442        except OSError:
1443            return False
1444
1445        dev = self.stat().st_dev
1446        if dev != parent_dev:
1447            return True
1448        ino = self.stat().st_ino
1449        parent_ino = parent.stat().st_ino
1450        return ino == parent_ino
1451
1452    def is_symlink(self):
1453        """
1454        Whether this path is a symbolic link.
1455        """
1456        try:
1457            return S_ISLNK(self.lstat().st_mode)
1458        except OSError as e:
1459            if not _ignore_error(e):
1460                raise
1461            # Path doesn't exist
1462            return False
1463        except ValueError:
1464            # Non-encodable path
1465            return False
1466
1467    def is_block_device(self):
1468        """
1469        Whether this path is a block device.
1470        """
1471        try:
1472            return S_ISBLK(self.stat().st_mode)
1473        except OSError as e:
1474            if not _ignore_error(e):
1475                raise
1476            # Path doesn't exist or is a broken symlink
1477            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1478            return False
1479        except ValueError:
1480            # Non-encodable path
1481            return False
1482
1483    def is_char_device(self):
1484        """
1485        Whether this path is a character device.
1486        """
1487        try:
1488            return S_ISCHR(self.stat().st_mode)
1489        except OSError as e:
1490            if not _ignore_error(e):
1491                raise
1492            # Path doesn't exist or is a broken symlink
1493            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1494            return False
1495        except ValueError:
1496            # Non-encodable path
1497            return False
1498
1499    def is_fifo(self):
1500        """
1501        Whether this path is a FIFO.
1502        """
1503        try:
1504            return S_ISFIFO(self.stat().st_mode)
1505        except OSError as e:
1506            if not _ignore_error(e):
1507                raise
1508            # Path doesn't exist or is a broken symlink
1509            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1510            return False
1511        except ValueError:
1512            # Non-encodable path
1513            return False
1514
1515    def is_socket(self):
1516        """
1517        Whether this path is a socket.
1518        """
1519        try:
1520            return S_ISSOCK(self.stat().st_mode)
1521        except OSError as e:
1522            if not _ignore_error(e):
1523                raise
1524            # Path doesn't exist or is a broken symlink
1525            # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1526            return False
1527        except ValueError:
1528            # Non-encodable path
1529            return False
1530
1531    def expanduser(self):
1532        """ Return a new path with expanded ~ and ~user constructs
1533        (as returned by os.path.expanduser)
1534        """
1535        if (not (self._drv or self._root) and
1536            self._parts and self._parts[0][:1] == '~'):
1537            homedir = self._flavour.gethomedir(self._parts[0][1:])
1538            return self._from_parts([homedir] + self._parts[1:])
1539
1540        return self
1541
1542
1543class PosixPath(Path, PurePosixPath):
1544    """Path subclass for non-Windows systems.
1545
1546    On a POSIX system, instantiating a Path should return this object.
1547    """
1548    __slots__ = ()
1549
1550class WindowsPath(Path, PureWindowsPath):
1551    """Path subclass for Windows systems.
1552
1553    On a Windows system, instantiating a Path should return this object.
1554    """
1555    __slots__ = ()
1556
1557    def owner(self):
1558        raise NotImplementedError("Path.owner() is unsupported on this system")
1559
1560    def group(self):
1561        raise NotImplementedError("Path.group() is unsupported on this system")
1562
1563    def is_mount(self):
1564        raise NotImplementedError("Path.is_mount() is unsupported on this system")
1565