blob: 1b5ab387a6282f0a51f4d9a2fcb95c3f2ffda3ce [file] [log] [blame]
Antoine Pitrou31119e42013-11-22 17:38:12 +01001import fnmatch
2import functools
3import io
4import ntpath
5import os
6import posixpath
7import re
8import sys
Antoine Pitrou069a5e12013-12-03 09:41:35 +01009from collections import Sequence
Antoine Pitrou31119e42013-11-22 17:38:12 +010010from contextlib import contextmanager
Antoine Pitrou2b2852b2014-10-30 23:14:03 +010011from errno import EINVAL, ENOENT, ENOTDIR
Antoine Pitrou31119e42013-11-22 17:38:12 +010012from operator import attrgetter
13from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
Antoine Pitrou069a5e12013-12-03 09:41:35 +010014from urllib.parse import quote_from_bytes as urlquote_from_bytes
Antoine Pitrou31119e42013-11-22 17:38:12 +010015
16
17supports_symlinks = True
Antoine Pitroudb118f52014-11-19 00:32:08 +010018if os.name == 'nt':
Antoine Pitrou31119e42013-11-22 17:38:12 +010019 import nt
Antoine Pitrou31119e42013-11-22 17:38:12 +010020 if sys.getwindowsversion()[:2] >= (6, 0):
21 from nt import _getfinalpathname
22 else:
23 supports_symlinks = False
24 _getfinalpathname = None
Antoine Pitroudb118f52014-11-19 00:32:08 +010025else:
26 nt = None
Antoine Pitrou31119e42013-11-22 17:38:12 +010027
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:
Antoine Pitrou57fffd62015-02-15 18:03:59 +010076 if not part:
77 continue
78 if altsep:
79 part = part.replace(altsep, sep)
Antoine Pitrou31119e42013-11-22 17:38:12 +010080 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:
Serhiy Storchakaa9939022013-12-06 17:14:12 +020095 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:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100101 else:
Serhiy Storchakaa9939022013-12-06 17:14:12 +0200102 # Second path is non-anchored (common case)
103 return drv, root, parts + parts2
104 return drv2, root2, parts2
Antoine Pitrou31119e42013-11-22 17:38:12 +0100105
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
Antoine Pitroudb118f52014-11-19 00:32:08 +0100116 is_supported = (os.name == 'nt')
Antoine Pitrou31119e42013-11-22 17:38:12 +0100117
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):
182 s = str(path)
183 if not s:
184 return os.getcwd()
185 if _getfinalpathname is not None:
186 return self._ext_to_normal(_getfinalpathname(s))
187 # Means fallback on absolute
188 return None
189
190 def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix):
191 prefix = ''
192 if s.startswith(ext_prefix):
193 prefix = s[:4]
194 s = s[4:]
195 if s.startswith('UNC\\'):
196 prefix += s[:3]
197 s = '\\' + s[3:]
198 return prefix, s
199
200 def _ext_to_normal(self, s):
201 # Turn back an extended path into a normal DOS-like path
202 return self._split_extended_path(s)[1]
203
204 def is_reserved(self, parts):
205 # NOTE: the rules for reserved names seem somewhat complicated
206 # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
207 # We err on the side of caution and return True for paths which are
208 # not considered reserved by Windows.
209 if not parts:
210 return False
211 if parts[0].startswith('\\\\'):
212 # UNC paths are never reserved
213 return False
214 return parts[-1].partition('.')[0].upper() in self.reserved_names
215
216 def make_uri(self, path):
217 # Under Windows, file URIs use the UTF-8 encoding.
218 drive = path.drive
219 if len(drive) == 2 and drive[1] == ':':
220 # It's a path on a local drive => 'file:///c:/a/b'
221 rest = path.as_posix()[2:].lstrip('/')
222 return 'file:///%s/%s' % (
223 drive, urlquote_from_bytes(rest.encode('utf-8')))
224 else:
225 # It's a path on a network drive => 'file://host/share/a/b'
226 return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))
227
Antoine Pitrou8477ed62014-12-30 20:54:45 +0100228 def gethomedir(self, username):
229 if 'HOME' in os.environ:
230 userhome = os.environ['HOME']
231 elif 'USERPROFILE' in os.environ:
232 userhome = os.environ['USERPROFILE']
233 elif 'HOMEPATH' in os.environ:
Antoine Pitrou5d4e27e2014-12-30 22:09:42 +0100234 try:
235 drv = os.environ['HOMEDRIVE']
236 except KeyError:
237 drv = ''
238 userhome = drv + os.environ['HOMEPATH']
Antoine Pitrou8477ed62014-12-30 20:54:45 +0100239 else:
240 raise RuntimeError("Can't determine home directory")
241
242 if username:
243 # Try to guess user home directory. By default all users
244 # directories are located in the same place and are named by
245 # corresponding usernames. If current user home directory points
246 # to nonstandard place, this guess is likely wrong.
247 if os.environ['USERNAME'] != username:
248 drv, root, parts = self.parse_parts((userhome,))
249 if parts[-1] != os.environ['USERNAME']:
250 raise RuntimeError("Can't determine home directory "
251 "for %r" % username)
252 parts[-1] = username
253 if drv or root:
254 userhome = drv + root + self.join(parts[1:])
255 else:
256 userhome = self.join(parts)
257 return userhome
Antoine Pitrou31119e42013-11-22 17:38:12 +0100258
259class _PosixFlavour(_Flavour):
260 sep = '/'
261 altsep = ''
262 has_drv = False
263 pathmod = posixpath
264
265 is_supported = (os.name != 'nt')
266
267 def splitroot(self, part, sep=sep):
268 if part and part[0] == sep:
269 stripped_part = part.lstrip(sep)
270 # According to POSIX path resolution:
271 # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
272 # "A pathname that begins with two successive slashes may be
273 # interpreted in an implementation-defined manner, although more
274 # than two leading slashes shall be treated as a single slash".
275 if len(part) - len(stripped_part) == 2:
276 return '', sep * 2, stripped_part
277 else:
278 return '', sep, stripped_part
279 else:
280 return '', '', part
281
282 def casefold(self, s):
283 return s
284
285 def casefold_parts(self, parts):
286 return parts
287
288 def resolve(self, path):
289 sep = self.sep
Antoine Pitrou31119e42013-11-22 17:38:12 +0100290 accessor = path._accessor
Antoine Pitrouc274fd22013-12-16 19:57:41 +0100291 seen = {}
292 def _resolve(path, rest):
293 if rest.startswith(sep):
294 path = ''
295
296 for name in rest.split(sep):
297 if not name or name == '.':
298 # current dir
299 continue
300 if name == '..':
301 # parent dir
302 path, _, _ = path.rpartition(sep)
303 continue
304 newpath = path + sep + name
305 if newpath in seen:
306 # Already seen this path
307 path = seen[newpath]
308 if path is not None:
309 # use cached value
310 continue
311 # The symlink is not resolved, so we must have a symlink loop.
312 raise RuntimeError("Symlink loop from %r" % newpath)
313 # Resolve the symbolic link
314 try:
315 target = accessor.readlink(newpath)
316 except OSError as e:
317 if e.errno != EINVAL:
318 raise
319 # Not a symlink
320 path = newpath
321 else:
322 seen[newpath] = None # not resolved symlink
323 path = _resolve(path, target)
324 seen[newpath] = path # resolved symlink
325
326 return path
327 # NOTE: according to POSIX, getcwd() cannot contain path components
328 # which are symlinks.
329 base = '' if path.is_absolute() else os.getcwd()
330 return _resolve(base, str(path)) or sep
Antoine Pitrou31119e42013-11-22 17:38:12 +0100331
332 def is_reserved(self, parts):
333 return False
334
335 def make_uri(self, path):
336 # We represent the path using the local filesystem encoding,
337 # for portability to other applications.
338 bpath = bytes(path)
339 return 'file://' + urlquote_from_bytes(bpath)
340
Antoine Pitrou8477ed62014-12-30 20:54:45 +0100341 def gethomedir(self, username):
342 if not username:
343 try:
344 return os.environ['HOME']
345 except KeyError:
346 import pwd
347 return pwd.getpwuid(os.getuid()).pw_dir
348 else:
349 import pwd
350 try:
351 return pwd.getpwnam(username).pw_dir
352 except KeyError:
353 raise RuntimeError("Can't determine home directory "
354 "for %r" % username)
355
Antoine Pitrou31119e42013-11-22 17:38:12 +0100356
357_windows_flavour = _WindowsFlavour()
358_posix_flavour = _PosixFlavour()
359
360
361class _Accessor:
362 """An accessor implements a particular (system-specific or not) way of
363 accessing paths on the filesystem."""
364
365
366class _NormalAccessor(_Accessor):
367
368 def _wrap_strfunc(strfunc):
369 @functools.wraps(strfunc)
370 def wrapped(pathobj, *args):
371 return strfunc(str(pathobj), *args)
372 return staticmethod(wrapped)
373
374 def _wrap_binary_strfunc(strfunc):
375 @functools.wraps(strfunc)
376 def wrapped(pathobjA, pathobjB, *args):
377 return strfunc(str(pathobjA), str(pathobjB), *args)
378 return staticmethod(wrapped)
379
380 stat = _wrap_strfunc(os.stat)
381
382 lstat = _wrap_strfunc(os.lstat)
383
384 open = _wrap_strfunc(os.open)
385
386 listdir = _wrap_strfunc(os.listdir)
387
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300388 scandir = _wrap_strfunc(os.scandir)
389
Antoine Pitrou31119e42013-11-22 17:38:12 +0100390 chmod = _wrap_strfunc(os.chmod)
391
392 if hasattr(os, "lchmod"):
393 lchmod = _wrap_strfunc(os.lchmod)
394 else:
395 def lchmod(self, pathobj, mode):
396 raise NotImplementedError("lchmod() not available on this system")
397
398 mkdir = _wrap_strfunc(os.mkdir)
399
400 unlink = _wrap_strfunc(os.unlink)
401
402 rmdir = _wrap_strfunc(os.rmdir)
403
404 rename = _wrap_binary_strfunc(os.rename)
405
406 replace = _wrap_binary_strfunc(os.replace)
407
408 if nt:
409 if supports_symlinks:
410 symlink = _wrap_binary_strfunc(os.symlink)
411 else:
412 def symlink(a, b, target_is_directory):
413 raise NotImplementedError("symlink() not available on this system")
414 else:
415 # Under POSIX, os.symlink() takes two args
416 @staticmethod
417 def symlink(a, b, target_is_directory):
418 return os.symlink(str(a), str(b))
419
420 utime = _wrap_strfunc(os.utime)
421
422 # Helper for resolve()
423 def readlink(self, path):
424 return os.readlink(path)
425
426
427_normal_accessor = _NormalAccessor()
428
429
430#
431# Globbing helpers
432#
433
Antoine Pitrou31119e42013-11-22 17:38:12 +0100434def _make_selector(pattern_parts):
435 pat = pattern_parts[0]
436 child_parts = pattern_parts[1:]
437 if pat == '**':
438 cls = _RecursiveWildcardSelector
439 elif '**' in pat:
440 raise ValueError("Invalid pattern: '**' can only be an entire path component")
441 elif _is_wildcard_pattern(pat):
442 cls = _WildcardSelector
443 else:
444 cls = _PreciseSelector
445 return cls(pat, child_parts)
446
447if hasattr(functools, "lru_cache"):
448 _make_selector = functools.lru_cache()(_make_selector)
449
450
451class _Selector:
452 """A selector matches a specific glob pattern part against the children
453 of a given path."""
454
455 def __init__(self, child_parts):
456 self.child_parts = child_parts
457 if child_parts:
458 self.successor = _make_selector(child_parts)
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300459 self.dironly = True
Antoine Pitrou31119e42013-11-22 17:38:12 +0100460 else:
461 self.successor = _TerminatingSelector()
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300462 self.dironly = False
Antoine Pitrou31119e42013-11-22 17:38:12 +0100463
464 def select_from(self, parent_path):
465 """Iterate over all child paths of `parent_path` matched by this
466 selector. This can contain parent_path itself."""
467 path_cls = type(parent_path)
468 is_dir = path_cls.is_dir
469 exists = path_cls.exists
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300470 scandir = parent_path._accessor.scandir
471 if not is_dir(parent_path):
472 return iter([])
473 return self._select_from(parent_path, is_dir, exists, scandir)
Antoine Pitrou31119e42013-11-22 17:38:12 +0100474
475
476class _TerminatingSelector:
477
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300478 def _select_from(self, parent_path, is_dir, exists, scandir):
Antoine Pitrou31119e42013-11-22 17:38:12 +0100479 yield parent_path
480
481
482class _PreciseSelector(_Selector):
483
484 def __init__(self, name, child_parts):
485 self.name = name
486 _Selector.__init__(self, child_parts)
487
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300488 def _select_from(self, parent_path, is_dir, exists, scandir):
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800489 try:
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800490 path = parent_path._make_child_relpath(self.name)
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300491 if (is_dir if self.dironly else exists)(path):
492 for p in self.successor._select_from(path, is_dir, exists, scandir):
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800493 yield p
494 except PermissionError:
Antoine Pitrou31119e42013-11-22 17:38:12 +0100495 return
Antoine Pitrou31119e42013-11-22 17:38:12 +0100496
497
498class _WildcardSelector(_Selector):
499
500 def __init__(self, pat, child_parts):
501 self.pat = re.compile(fnmatch.translate(pat))
502 _Selector.__init__(self, child_parts)
503
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300504 def _select_from(self, parent_path, is_dir, exists, scandir):
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800505 try:
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800506 cf = parent_path._flavour.casefold
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300507 entries = list(scandir(parent_path))
508 for entry in entries:
509 if not self.dironly or entry.is_dir():
510 name = entry.name
511 casefolded = cf(name)
512 if self.pat.match(casefolded):
513 path = parent_path._make_child_relpath(name)
514 for p in self.successor._select_from(path, is_dir, exists, scandir):
515 yield p
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800516 except PermissionError:
Antoine Pitrou31119e42013-11-22 17:38:12 +0100517 return
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800518
Antoine Pitrou31119e42013-11-22 17:38:12 +0100519
520
521class _RecursiveWildcardSelector(_Selector):
522
523 def __init__(self, pat, child_parts):
524 _Selector.__init__(self, child_parts)
525
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300526 def _iterate_directories(self, parent_path, is_dir, scandir):
Antoine Pitrou31119e42013-11-22 17:38:12 +0100527 yield parent_path
Guido van Rossumbc9fdda2016-01-07 10:56:36 -0800528 try:
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300529 entries = list(scandir(parent_path))
530 for entry in entries:
531 if entry.is_dir() and not entry.is_symlink():
532 path = parent_path._make_child_relpath(entry.name)
533 for p in self._iterate_directories(path, is_dir, scandir):
Guido van Rossumbc9fdda2016-01-07 10:56:36 -0800534 yield p
535 except PermissionError:
536 return
Antoine Pitrou31119e42013-11-22 17:38:12 +0100537
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300538 def _select_from(self, parent_path, is_dir, exists, scandir):
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800539 try:
Serhiy Storchaka680cb152016-09-07 10:58:05 +0300540 yielded = set()
541 try:
542 successor_select = self.successor._select_from
543 for starting_point in self._iterate_directories(parent_path, is_dir, scandir):
544 for p in successor_select(starting_point, is_dir, exists, scandir):
545 if p not in yielded:
546 yield p
547 yielded.add(p)
548 finally:
549 yielded.clear()
Guido van Rossum6c2d33a2016-01-06 09:42:07 -0800550 except PermissionError:
Antoine Pitrou31119e42013-11-22 17:38:12 +0100551 return
Antoine Pitrou31119e42013-11-22 17:38:12 +0100552
553
554#
555# Public API
556#
557
558class _PathParents(Sequence):
559 """This object provides sequence-like access to the logical ancestors
560 of a path. Don't try to construct it yourself."""
561 __slots__ = ('_pathcls', '_drv', '_root', '_parts')
562
563 def __init__(self, path):
564 # We don't store the instance to avoid reference cycles
565 self._pathcls = type(path)
566 self._drv = path._drv
567 self._root = path._root
568 self._parts = path._parts
569
570 def __len__(self):
571 if self._drv or self._root:
572 return len(self._parts) - 1
573 else:
574 return len(self._parts)
575
576 def __getitem__(self, idx):
577 if idx < 0 or idx >= len(self):
578 raise IndexError(idx)
579 return self._pathcls._from_parsed_parts(self._drv, self._root,
580 self._parts[:-idx - 1])
581
582 def __repr__(self):
583 return "<{}.parents>".format(self._pathcls.__name__)
584
585
586class PurePath(object):
587 """PurePath represents a filesystem path and offers operations which
588 don't imply any actual filesystem I/O. Depending on your system,
589 instantiating a PurePath will return either a PurePosixPath or a
590 PureWindowsPath object. You can also instantiate either of these classes
591 directly, regardless of your system.
592 """
593 __slots__ = (
594 '_drv', '_root', '_parts',
595 '_str', '_hash', '_pparts', '_cached_cparts',
596 )
597
598 def __new__(cls, *args):
599 """Construct a PurePath from one or several strings and or existing
600 PurePath objects. The strings and path objects are combined so as
601 to yield a canonicalized path, which is incorporated into the
602 new PurePath object.
603 """
604 if cls is PurePath:
605 cls = PureWindowsPath if os.name == 'nt' else PurePosixPath
606 return cls._from_parts(args)
607
608 def __reduce__(self):
609 # Using the parts tuple helps share interned path parts
610 # when pickling related paths.
611 return (self.__class__, tuple(self._parts))
612
613 @classmethod
614 def _parse_args(cls, args):
615 # This is useful when you don't want to create an instance, just
616 # canonicalize some constructor arguments.
617 parts = []
618 for a in args:
619 if isinstance(a, PurePath):
620 parts += a._parts
Antoine Pitrou31119e42013-11-22 17:38:12 +0100621 else:
Brett Cannon568be632016-06-10 12:20:49 -0700622 a = os.fspath(a)
623 if isinstance(a, str):
624 # Force-cast str subclasses to str (issue #21127)
625 parts.append(str(a))
626 else:
627 raise TypeError(
628 "argument should be a str object or an os.PathLike "
629 "object returning str, not %r"
630 % type(a))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100631 return cls._flavour.parse_parts(parts)
632
633 @classmethod
634 def _from_parts(cls, args, init=True):
635 # We need to call _parse_args on the instance, so as to get the
636 # right flavour.
637 self = object.__new__(cls)
638 drv, root, parts = self._parse_args(args)
639 self._drv = drv
640 self._root = root
641 self._parts = parts
642 if init:
643 self._init()
644 return self
645
646 @classmethod
647 def _from_parsed_parts(cls, drv, root, parts, init=True):
648 self = object.__new__(cls)
649 self._drv = drv
650 self._root = root
651 self._parts = parts
652 if init:
653 self._init()
654 return self
655
656 @classmethod
657 def _format_parsed_parts(cls, drv, root, parts):
658 if drv or root:
659 return drv + root + cls._flavour.join(parts[1:])
660 else:
661 return cls._flavour.join(parts)
662
663 def _init(self):
Martin Pantere26da7c2016-06-02 10:07:09 +0000664 # Overridden in concrete Path
Antoine Pitrou31119e42013-11-22 17:38:12 +0100665 pass
666
667 def _make_child(self, args):
668 drv, root, parts = self._parse_args(args)
669 drv, root, parts = self._flavour.join_parsed_parts(
670 self._drv, self._root, self._parts, drv, root, parts)
671 return self._from_parsed_parts(drv, root, parts)
672
673 def __str__(self):
674 """Return the string representation of the path, suitable for
675 passing to system calls."""
676 try:
677 return self._str
678 except AttributeError:
679 self._str = self._format_parsed_parts(self._drv, self._root,
680 self._parts) or '.'
681 return self._str
682
Brett Cannon568be632016-06-10 12:20:49 -0700683 def __fspath__(self):
684 return str(self)
685
Antoine Pitrou31119e42013-11-22 17:38:12 +0100686 def as_posix(self):
687 """Return the string representation of the path with forward (/)
688 slashes."""
689 f = self._flavour
690 return str(self).replace(f.sep, '/')
691
692 def __bytes__(self):
693 """Return the bytes representation of the path. This is only
694 recommended to use under Unix."""
695 return os.fsencode(str(self))
696
697 def __repr__(self):
698 return "{}({!r})".format(self.__class__.__name__, self.as_posix())
699
700 def as_uri(self):
701 """Return the path as a 'file' URI."""
702 if not self.is_absolute():
703 raise ValueError("relative path can't be expressed as a file URI")
704 return self._flavour.make_uri(self)
705
706 @property
707 def _cparts(self):
708 # Cached casefolded parts, for hashing and comparison
709 try:
710 return self._cached_cparts
711 except AttributeError:
712 self._cached_cparts = self._flavour.casefold_parts(self._parts)
713 return self._cached_cparts
714
715 def __eq__(self, other):
716 if not isinstance(other, PurePath):
717 return NotImplemented
718 return self._cparts == other._cparts and self._flavour is other._flavour
719
Antoine Pitrou31119e42013-11-22 17:38:12 +0100720 def __hash__(self):
721 try:
722 return self._hash
723 except AttributeError:
724 self._hash = hash(tuple(self._cparts))
725 return self._hash
726
727 def __lt__(self, other):
728 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
729 return NotImplemented
730 return self._cparts < other._cparts
731
732 def __le__(self, other):
733 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
734 return NotImplemented
735 return self._cparts <= other._cparts
736
737 def __gt__(self, other):
738 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
739 return NotImplemented
740 return self._cparts > other._cparts
741
742 def __ge__(self, other):
743 if not isinstance(other, PurePath) or self._flavour is not other._flavour:
744 return NotImplemented
745 return self._cparts >= other._cparts
746
747 drive = property(attrgetter('_drv'),
748 doc="""The drive prefix (letter or UNC path), if any.""")
749
750 root = property(attrgetter('_root'),
751 doc="""The root of the path, if any.""")
752
753 @property
754 def anchor(self):
755 """The concatenation of the drive and root, or ''."""
756 anchor = self._drv + self._root
757 return anchor
758
759 @property
760 def name(self):
761 """The final path component, if any."""
762 parts = self._parts
763 if len(parts) == (1 if (self._drv or self._root) else 0):
764 return ''
765 return parts[-1]
766
767 @property
768 def suffix(self):
769 """The final component's last suffix, if any."""
770 name = self.name
771 i = name.rfind('.')
772 if 0 < i < len(name) - 1:
773 return name[i:]
774 else:
775 return ''
776
777 @property
778 def suffixes(self):
779 """A list of the final component's suffixes, if any."""
780 name = self.name
781 if name.endswith('.'):
782 return []
783 name = name.lstrip('.')
784 return ['.' + suffix for suffix in name.split('.')[1:]]
785
786 @property
787 def stem(self):
788 """The final path component, minus its last suffix."""
789 name = self.name
790 i = name.rfind('.')
791 if 0 < i < len(name) - 1:
792 return name[:i]
793 else:
794 return name
795
796 def with_name(self, name):
797 """Return a new path with the file name changed."""
798 if not self.name:
799 raise ValueError("%r has an empty name" % (self,))
Antoine Pitrou7084e732014-07-06 21:31:12 -0400800 drv, root, parts = self._flavour.parse_parts((name,))
801 if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep]
802 or drv or root or len(parts) != 1):
803 raise ValueError("Invalid name %r" % (name))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100804 return self._from_parsed_parts(self._drv, self._root,
805 self._parts[:-1] + [name])
806
807 def with_suffix(self, suffix):
808 """Return a new path with the file suffix changed (or added, if none)."""
809 # XXX if suffix is None, should the current suffix be removed?
Antoine Pitroue50dafc2014-07-06 21:37:15 -0400810 f = self._flavour
811 if f.sep in suffix or f.altsep and f.altsep in suffix:
Antoine Pitrou1b02da92014-01-03 00:07:17 +0100812 raise ValueError("Invalid suffix %r" % (suffix))
Antoine Pitroue50dafc2014-07-06 21:37:15 -0400813 if suffix and not suffix.startswith('.') or suffix == '.':
Antoine Pitrou1b02da92014-01-03 00:07:17 +0100814 raise ValueError("Invalid suffix %r" % (suffix))
Antoine Pitrou31119e42013-11-22 17:38:12 +0100815 name = self.name
816 if not name:
817 raise ValueError("%r has an empty name" % (self,))
818 old_suffix = self.suffix
819 if not old_suffix:
820 name = name + suffix
821 else:
822 name = name[:-len(old_suffix)] + suffix
823 return self._from_parsed_parts(self._drv, self._root,
824 self._parts[:-1] + [name])
825
826 def relative_to(self, *other):
827 """Return the relative path to another path identified by the passed
828 arguments. If the operation is not possible (because this is not
829 a subpath of the other path), raise ValueError.
830 """
831 # For the purpose of this method, drive and root are considered
832 # separate parts, i.e.:
833 # Path('c:/').relative_to('c:') gives Path('/')
834 # Path('c:/').relative_to('/') raise ValueError
835 if not other:
836 raise TypeError("need at least one argument")
837 parts = self._parts
838 drv = self._drv
839 root = self._root
Antoine Pitrou156b3612013-12-28 19:49:04 +0100840 if root:
841 abs_parts = [drv, root] + parts[1:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100842 else:
843 abs_parts = parts
844 to_drv, to_root, to_parts = self._parse_args(other)
Antoine Pitrou156b3612013-12-28 19:49:04 +0100845 if to_root:
846 to_abs_parts = [to_drv, to_root] + to_parts[1:]
Antoine Pitrou31119e42013-11-22 17:38:12 +0100847 else:
848 to_abs_parts = to_parts
849 n = len(to_abs_parts)
Antoine Pitrou156b3612013-12-28 19:49:04 +0100850 cf = self._flavour.casefold_parts
851 if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts):
Antoine Pitrou31119e42013-11-22 17:38:12 +0100852 formatted = self._format_parsed_parts(to_drv, to_root, to_parts)
853 raise ValueError("{!r} does not start with {!r}"
854 .format(str(self), str(formatted)))
Antoine Pitrou156b3612013-12-28 19:49:04 +0100855 return self._from_parsed_parts('', root if n == 1 else '',
856 abs_parts[n:])
Antoine Pitrou31119e42013-11-22 17:38:12 +0100857
858 @property
859 def parts(self):
860 """An object providing sequence-like access to the
861 components in the filesystem path."""
862 # We cache the tuple to avoid building a new one each time .parts
863 # is accessed. XXX is this necessary?
864 try:
865 return self._pparts
866 except AttributeError:
867 self._pparts = tuple(self._parts)
868 return self._pparts
869
870 def joinpath(self, *args):
871 """Combine this path with one or several arguments, and return a
872 new path representing either a subpath (if all arguments are relative
873 paths) or a totally different path (if one of the arguments is
874 anchored).
875 """
876 return self._make_child(args)
877
878 def __truediv__(self, key):
879 return self._make_child((key,))
880
881 def __rtruediv__(self, key):
882 return self._from_parts([key] + self._parts)
883
884 @property
885 def parent(self):
886 """The logical parent of the path."""
887 drv = self._drv
888 root = self._root
889 parts = self._parts
890 if len(parts) == 1 and (drv or root):
891 return self
892 return self._from_parsed_parts(drv, root, parts[:-1])
893
894 @property
895 def parents(self):
896 """A sequence of this path's logical parents."""
897 return _PathParents(self)
898
899 def is_absolute(self):
900 """True if the path is absolute (has both a root and, if applicable,
901 a drive)."""
902 if not self._root:
903 return False
904 return not self._flavour.has_drv or bool(self._drv)
905
906 def is_reserved(self):
907 """Return True if the path contains one of the special names reserved
908 by the system, if any."""
909 return self._flavour.is_reserved(self._parts)
910
911 def match(self, path_pattern):
912 """
913 Return True if this path matches the given pattern.
914 """
915 cf = self._flavour.casefold
916 path_pattern = cf(path_pattern)
917 drv, root, pat_parts = self._flavour.parse_parts((path_pattern,))
918 if not pat_parts:
919 raise ValueError("empty pattern")
920 if drv and drv != cf(self._drv):
921 return False
922 if root and root != cf(self._root):
923 return False
924 parts = self._cparts
925 if drv or root:
926 if len(pat_parts) != len(parts):
927 return False
928 pat_parts = pat_parts[1:]
929 elif len(pat_parts) > len(parts):
930 return False
931 for part, pat in zip(reversed(parts), reversed(pat_parts)):
932 if not fnmatch.fnmatchcase(part, pat):
933 return False
934 return True
935
Brett Cannon568be632016-06-10 12:20:49 -0700936# Can't subclass os.PathLike from PurePath and keep the constructor
937# optimizations in PurePath._parse_args().
938os.PathLike.register(PurePath)
939
Antoine Pitrou31119e42013-11-22 17:38:12 +0100940
941class PurePosixPath(PurePath):
942 _flavour = _posix_flavour
943 __slots__ = ()
944
945
946class PureWindowsPath(PurePath):
947 _flavour = _windows_flavour
948 __slots__ = ()
949
950
951# Filesystem-accessing classes
952
953
954class Path(PurePath):
955 __slots__ = (
956 '_accessor',
957 '_closed',
958 )
959
960 def __new__(cls, *args, **kwargs):
961 if cls is Path:
962 cls = WindowsPath if os.name == 'nt' else PosixPath
963 self = cls._from_parts(args, init=False)
964 if not self._flavour.is_supported:
965 raise NotImplementedError("cannot instantiate %r on your system"
966 % (cls.__name__,))
967 self._init()
968 return self
969
970 def _init(self,
971 # Private non-constructor arguments
972 template=None,
973 ):
974 self._closed = False
975 if template is not None:
976 self._accessor = template._accessor
977 else:
978 self._accessor = _normal_accessor
979
980 def _make_child_relpath(self, part):
981 # This is an optimization used for dir walking. `part` must be
982 # a single part relative to this path.
983 parts = self._parts + [part]
984 return self._from_parsed_parts(self._drv, self._root, parts)
985
986 def __enter__(self):
987 if self._closed:
988 self._raise_closed()
989 return self
990
991 def __exit__(self, t, v, tb):
992 self._closed = True
993
994 def _raise_closed(self):
995 raise ValueError("I/O operation on closed path")
996
997 def _opener(self, name, flags, mode=0o666):
998 # A stub for the opener argument to built-in open()
999 return self._accessor.open(self, flags, mode)
1000
Antoine Pitrou4a60d422013-12-02 21:25:18 +01001001 def _raw_open(self, flags, mode=0o777):
1002 """
1003 Open the file pointed by this path and return a file descriptor,
1004 as os.open() does.
1005 """
1006 if self._closed:
1007 self._raise_closed()
1008 return self._accessor.open(self, flags, mode)
1009
Antoine Pitrou31119e42013-11-22 17:38:12 +01001010 # Public API
1011
1012 @classmethod
1013 def cwd(cls):
1014 """Return a new path pointing to the current working directory
1015 (as returned by os.getcwd()).
1016 """
1017 return cls(os.getcwd())
1018
Antoine Pitrou17cba7d2015-01-12 21:03:41 +01001019 @classmethod
1020 def home(cls):
1021 """Return a new path pointing to the user's home directory (as
1022 returned by os.path.expanduser('~')).
1023 """
1024 return cls(cls()._flavour.gethomedir(None))
1025
Antoine Pitrou43e3d942014-05-13 10:50:15 +02001026 def samefile(self, other_path):
Berker Peksag05492b82015-10-22 03:34:16 +03001027 """Return whether other_path is the same or not as this file
Berker Peksag267597f2015-10-21 20:10:24 +03001028 (as returned by os.path.samefile()).
Antoine Pitrou43e3d942014-05-13 10:50:15 +02001029 """
1030 st = self.stat()
1031 try:
1032 other_st = other_path.stat()
1033 except AttributeError:
1034 other_st = os.stat(other_path)
1035 return os.path.samestat(st, other_st)
1036
Antoine Pitrou31119e42013-11-22 17:38:12 +01001037 def iterdir(self):
1038 """Iterate over the files in this directory. Does not yield any
1039 result for the special paths '.' and '..'.
1040 """
1041 if self._closed:
1042 self._raise_closed()
1043 for name in self._accessor.listdir(self):
1044 if name in {'.', '..'}:
1045 # Yielding a path object for these makes little sense
1046 continue
1047 yield self._make_child_relpath(name)
1048 if self._closed:
1049 self._raise_closed()
1050
1051 def glob(self, pattern):
1052 """Iterate over this subtree and yield all existing files (of any
1053 kind, including directories) matching the given pattern.
1054 """
Berker Peksag4a208e42016-01-30 17:50:48 +02001055 if not pattern:
1056 raise ValueError("Unacceptable pattern: {!r}".format(pattern))
Antoine Pitrou31119e42013-11-22 17:38:12 +01001057 pattern = self._flavour.casefold(pattern)
1058 drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1059 if drv or root:
1060 raise NotImplementedError("Non-relative patterns are unsupported")
1061 selector = _make_selector(tuple(pattern_parts))
1062 for p in selector.select_from(self):
1063 yield p
1064
1065 def rglob(self, pattern):
1066 """Recursively yield all existing files (of any kind, including
1067 directories) matching the given pattern, anywhere in this subtree.
1068 """
1069 pattern = self._flavour.casefold(pattern)
1070 drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
1071 if drv or root:
1072 raise NotImplementedError("Non-relative patterns are unsupported")
1073 selector = _make_selector(("**",) + tuple(pattern_parts))
1074 for p in selector.select_from(self):
1075 yield p
1076
1077 def absolute(self):
1078 """Return an absolute version of this path. This function works
1079 even if the path doesn't point to anything.
1080
1081 No normalization is done, i.e. all '.' and '..' will be kept along.
1082 Use resolve() to get the canonical path to a file.
1083 """
1084 # XXX untested yet!
1085 if self._closed:
1086 self._raise_closed()
1087 if self.is_absolute():
1088 return self
1089 # FIXME this must defer to the specific flavour (and, under Windows,
1090 # use nt._getfullpathname())
1091 obj = self._from_parts([os.getcwd()] + self._parts, init=False)
1092 obj._init(template=self)
1093 return obj
1094
1095 def resolve(self):
1096 """
1097 Make the path absolute, resolving all symlinks on the way and also
1098 normalizing it (for example turning slashes into backslashes under
1099 Windows).
1100 """
1101 if self._closed:
1102 self._raise_closed()
1103 s = self._flavour.resolve(self)
1104 if s is None:
1105 # No symlink resolution => for consistency, raise an error if
1106 # the path doesn't exist or is forbidden
1107 self.stat()
1108 s = str(self.absolute())
1109 # Now we have no symlinks in the path, it's safe to normalize it.
1110 normed = self._flavour.pathmod.normpath(s)
1111 obj = self._from_parts((normed,), init=False)
1112 obj._init(template=self)
1113 return obj
1114
1115 def stat(self):
1116 """
1117 Return the result of the stat() system call on this path, like
1118 os.stat() does.
1119 """
1120 return self._accessor.stat(self)
1121
1122 def owner(self):
1123 """
1124 Return the login name of the file owner.
1125 """
1126 import pwd
1127 return pwd.getpwuid(self.stat().st_uid).pw_name
1128
1129 def group(self):
1130 """
1131 Return the group name of the file gid.
1132 """
1133 import grp
1134 return grp.getgrgid(self.stat().st_gid).gr_name
1135
Antoine Pitrou31119e42013-11-22 17:38:12 +01001136 def open(self, mode='r', buffering=-1, encoding=None,
1137 errors=None, newline=None):
1138 """
1139 Open the file pointed by this path and return a file object, as
1140 the built-in open() function does.
1141 """
1142 if self._closed:
1143 self._raise_closed()
1144 return io.open(str(self), mode, buffering, encoding, errors, newline,
1145 opener=self._opener)
1146
Georg Brandlea683982014-10-01 19:12:33 +02001147 def read_bytes(self):
1148 """
1149 Open the file in bytes mode, read it, and close the file.
1150 """
1151 with self.open(mode='rb') as f:
1152 return f.read()
1153
1154 def read_text(self, encoding=None, errors=None):
1155 """
1156 Open the file in text mode, read it, and close the file.
1157 """
1158 with self.open(mode='r', encoding=encoding, errors=errors) as f:
1159 return f.read()
1160
1161 def write_bytes(self, data):
1162 """
1163 Open the file in bytes mode, write to it, and close the file.
1164 """
1165 # type-check for the buffer interface before truncating the file
1166 view = memoryview(data)
1167 with self.open(mode='wb') as f:
1168 return f.write(view)
1169
1170 def write_text(self, data, encoding=None, errors=None):
1171 """
1172 Open the file in text mode, write to it, and close the file.
1173 """
1174 if not isinstance(data, str):
1175 raise TypeError('data must be str, not %s' %
1176 data.__class__.__name__)
1177 with self.open(mode='w', encoding=encoding, errors=errors) as f:
1178 return f.write(data)
1179
Antoine Pitrou31119e42013-11-22 17:38:12 +01001180 def touch(self, mode=0o666, exist_ok=True):
1181 """
1182 Create this file with the given access mode, if it doesn't exist.
1183 """
1184 if self._closed:
1185 self._raise_closed()
1186 if exist_ok:
1187 # First try to bump modification time
1188 # Implementation note: GNU touch uses the UTIME_NOW option of
1189 # the utimensat() / futimens() functions.
Antoine Pitrou31119e42013-11-22 17:38:12 +01001190 try:
Antoine Pitrou2cf39172013-11-23 15:25:59 +01001191 self._accessor.utime(self, None)
Antoine Pitrou31119e42013-11-22 17:38:12 +01001192 except OSError:
1193 # Avoid exception chaining
1194 pass
1195 else:
1196 return
1197 flags = os.O_CREAT | os.O_WRONLY
1198 if not exist_ok:
1199 flags |= os.O_EXCL
1200 fd = self._raw_open(flags, mode)
1201 os.close(fd)
1202
Barry Warsaw7c549c42014-08-05 11:28:12 -04001203 def mkdir(self, mode=0o777, parents=False, exist_ok=False):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001204 if self._closed:
1205 self._raise_closed()
1206 if not parents:
Barry Warsaw7c549c42014-08-05 11:28:12 -04001207 try:
1208 self._accessor.mkdir(self, mode)
1209 except FileExistsError:
1210 if not exist_ok or not self.is_dir():
1211 raise
Antoine Pitrou31119e42013-11-22 17:38:12 +01001212 else:
1213 try:
1214 self._accessor.mkdir(self, mode)
Barry Warsaw7c549c42014-08-05 11:28:12 -04001215 except FileExistsError:
1216 if not exist_ok or not self.is_dir():
1217 raise
Antoine Pitrou31119e42013-11-22 17:38:12 +01001218 except OSError as e:
1219 if e.errno != ENOENT:
1220 raise
Antoine Pitrou0048c982013-12-16 20:22:37 +01001221 self.parent.mkdir(parents=True)
Antoine Pitrou31119e42013-11-22 17:38:12 +01001222 self._accessor.mkdir(self, mode)
1223
1224 def chmod(self, mode):
1225 """
1226 Change the permissions of the path, like os.chmod().
1227 """
1228 if self._closed:
1229 self._raise_closed()
1230 self._accessor.chmod(self, mode)
1231
1232 def lchmod(self, mode):
1233 """
1234 Like chmod(), except if the path points to a symlink, the symlink's
1235 permissions are changed, rather than its target's.
1236 """
1237 if self._closed:
1238 self._raise_closed()
1239 self._accessor.lchmod(self, mode)
1240
1241 def unlink(self):
1242 """
1243 Remove this file or link.
1244 If the path is a directory, use rmdir() instead.
1245 """
1246 if self._closed:
1247 self._raise_closed()
1248 self._accessor.unlink(self)
1249
1250 def rmdir(self):
1251 """
1252 Remove this directory. The directory must be empty.
1253 """
1254 if self._closed:
1255 self._raise_closed()
1256 self._accessor.rmdir(self)
1257
1258 def lstat(self):
1259 """
1260 Like stat(), except if the path points to a symlink, the symlink's
1261 status information is returned, rather than its target's.
1262 """
1263 if self._closed:
1264 self._raise_closed()
1265 return self._accessor.lstat(self)
1266
1267 def rename(self, target):
1268 """
1269 Rename this path to the given path.
1270 """
1271 if self._closed:
1272 self._raise_closed()
1273 self._accessor.rename(self, target)
1274
1275 def replace(self, target):
1276 """
1277 Rename this path to the given path, clobbering the existing
1278 destination if it exists.
1279 """
1280 if self._closed:
1281 self._raise_closed()
1282 self._accessor.replace(self, target)
1283
1284 def symlink_to(self, target, target_is_directory=False):
1285 """
1286 Make this path a symlink pointing to the given path.
1287 Note the order of arguments (self, target) is the reverse of os.symlink's.
1288 """
1289 if self._closed:
1290 self._raise_closed()
1291 self._accessor.symlink(target, self, target_is_directory)
1292
1293 # Convenience functions for querying the stat results
1294
1295 def exists(self):
1296 """
1297 Whether this path exists.
1298 """
1299 try:
1300 self.stat()
1301 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001302 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001303 raise
1304 return False
1305 return True
1306
1307 def is_dir(self):
1308 """
1309 Whether this path is a directory.
1310 """
1311 try:
1312 return S_ISDIR(self.stat().st_mode)
1313 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001314 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001315 raise
1316 # Path doesn't exist or is a broken symlink
1317 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1318 return False
1319
1320 def is_file(self):
1321 """
1322 Whether this path is a regular file (also True for symlinks pointing
1323 to regular files).
1324 """
1325 try:
1326 return S_ISREG(self.stat().st_mode)
1327 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001328 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001329 raise
1330 # Path doesn't exist or is a broken symlink
1331 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1332 return False
1333
1334 def is_symlink(self):
1335 """
1336 Whether this path is a symbolic link.
1337 """
1338 try:
1339 return S_ISLNK(self.lstat().st_mode)
1340 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001341 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001342 raise
1343 # Path doesn't exist
1344 return False
1345
1346 def is_block_device(self):
1347 """
1348 Whether this path is a block device.
1349 """
1350 try:
1351 return S_ISBLK(self.stat().st_mode)
1352 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001353 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001354 raise
1355 # Path doesn't exist or is a broken symlink
1356 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1357 return False
1358
1359 def is_char_device(self):
1360 """
1361 Whether this path is a character device.
1362 """
1363 try:
1364 return S_ISCHR(self.stat().st_mode)
1365 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001366 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001367 raise
1368 # Path doesn't exist or is a broken symlink
1369 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1370 return False
1371
1372 def is_fifo(self):
1373 """
1374 Whether this path is a FIFO.
1375 """
1376 try:
1377 return S_ISFIFO(self.stat().st_mode)
1378 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001379 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001380 raise
1381 # Path doesn't exist or is a broken symlink
1382 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1383 return False
1384
1385 def is_socket(self):
1386 """
1387 Whether this path is a socket.
1388 """
1389 try:
1390 return S_ISSOCK(self.stat().st_mode)
1391 except OSError as e:
Antoine Pitrou2b2852b2014-10-30 23:14:03 +01001392 if e.errno not in (ENOENT, ENOTDIR):
Antoine Pitrou31119e42013-11-22 17:38:12 +01001393 raise
1394 # Path doesn't exist or is a broken symlink
1395 # (see https://bitbucket.org/pitrou/pathlib/issue/12/)
1396 return False
1397
Antoine Pitrou8477ed62014-12-30 20:54:45 +01001398 def expanduser(self):
1399 """ Return a new path with expanded ~ and ~user constructs
1400 (as returned by os.path.expanduser)
1401 """
1402 if (not (self._drv or self._root) and
1403 self._parts and self._parts[0][:1] == '~'):
1404 homedir = self._flavour.gethomedir(self._parts[0][1:])
1405 return self._from_parts([homedir] + self._parts[1:])
1406
1407 return self
1408
Antoine Pitrou31119e42013-11-22 17:38:12 +01001409
1410class PosixPath(Path, PurePosixPath):
1411 __slots__ = ()
1412
1413class WindowsPath(Path, PureWindowsPath):
1414 __slots__ = ()
Berker Peksag04d42292016-03-11 23:07:27 +02001415
1416 def owner(self):
1417 raise NotImplementedError("Path.owner() is unsupported on this system")
1418
1419 def group(self):
1420 raise NotImplementedError("Path.group() is unsupported on this system")