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