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