1 # Module 'ntpath' -- common operations on WinNT/Win95 pathnames
2 """Common pathname manipulations, WindowsNT/95 version.
3
4 Instead of importing this module directly, import os and refer to this
5 module as os.path.
6 """
7
8 # strings representing various path-related bits and pieces
9 # These are primarily for export; internally, they are hardcoded.
10 # Should be set before imports for resolving cyclic dependency.
11 curdir = '.'
12 pardir = '..'
13 extsep = '.'
14 sep = '\\'
15 pathsep = ';'
16 altsep = '/'
17 defpath = '.;C:\\bin'
18 devnull = 'nul'
19
20 import os
21 import sys
22 import stat
23 import genericpath
24 from genericpath import *
25
26
27 __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
28 "basename","dirname","commonprefix","getsize","getmtime",
29 "getatime","getctime", "islink","exists","lexists","isdir","isfile",
30 "ismount", "expanduser","expandvars","normpath","abspath",
31 "curdir","pardir","sep","pathsep","defpath","altsep",
32 "extsep","devnull","realpath","supports_unicode_filenames","relpath",
33 "samefile", "sameopenfile", "samestat", "commonpath"]
34
35 def _get_bothseps(path):
36 if isinstance(path, bytes):
37 return b'\\/'
38 else:
39 return '\\/'
40
41 # Normalize the case of a pathname and map slashes to backslashes.
42 # Other normalizations (such as optimizing '../' away) are not done
43 # (this is done by normpath).
44
45 try:
46 from _winapi import (
47 LCMapStringEx as _LCMapStringEx,
48 LOCALE_NAME_INVARIANT as _LOCALE_NAME_INVARIANT,
49 LCMAP_LOWERCASE as _LCMAP_LOWERCASE)
50
51 def normcase(s):
52 """Normalize case of pathname.
53
54 Makes all characters lowercase and all slashes into backslashes.
55 """
56 s = os.fspath(s)
57 if not s:
58 return s
59 if isinstance(s, bytes):
60 encoding = sys.getfilesystemencoding()
61 s = s.decode(encoding, 'surrogateescape').replace('/', '\\')
62 s = _LCMapStringEx(_LOCALE_NAME_INVARIANT,
63 _LCMAP_LOWERCASE, s)
64 return s.encode(encoding, 'surrogateescape')
65 else:
66 return _LCMapStringEx(_LOCALE_NAME_INVARIANT,
67 _LCMAP_LOWERCASE,
68 s.replace('/', '\\'))
69 except ImportError:
70 def normcase(s):
71 """Normalize case of pathname.
72
73 Makes all characters lowercase and all slashes into backslashes.
74 """
75 s = os.fspath(s)
76 if isinstance(s, bytes):
77 return os.fsencode(os.fsdecode(s).replace('/', '\\').lower())
78 return s.replace('/', '\\').lower()
79
80
81 # Return whether a path is absolute.
82 # Trivial in Posix, harder on Windows.
83 # For Windows it is absolute if it starts with a slash or backslash (current
84 # volume), or if a pathname after the volume-letter-and-colon or UNC-resource
85 # starts with a slash or backslash.
86
87 def isabs(s):
88 """Test whether a path is absolute"""
89 s = os.fspath(s)
90 if isinstance(s, bytes):
91 sep = b'\\'
92 altsep = b'/'
93 colon_sep = b':\\'
94 else:
95 sep = '\\'
96 altsep = '/'
97 colon_sep = ':\\'
98 s = s[:3].replace(altsep, sep)
99 # Absolute: UNC, device, and paths with a drive and root.
100 # LEGACY BUG: isabs("/x") should be false since the path has no drive.
101 if s.startswith(sep) or s.startswith(colon_sep, 1):
102 return True
103 return False
104
105
106 # Join two (or more) paths.
107 def join(path, *paths):
108 path = os.fspath(path)
109 if isinstance(path, bytes):
110 sep = b'\\'
111 seps = b'\\/'
112 colon = b':'
113 else:
114 sep = '\\'
115 seps = '\\/'
116 colon = ':'
117 try:
118 if not paths:
119 path[:0] + sep #23780: Ensure compatible data type even if p is null.
120 result_drive, result_path = splitdrive(path)
121 for p in map(os.fspath, paths):
122 p_drive, p_path = splitdrive(p)
123 if p_path and p_path[0] in seps:
124 # Second path is absolute
125 if p_drive or not result_drive:
126 result_drive = p_drive
127 result_path = p_path
128 continue
129 elif p_drive and p_drive != result_drive:
130 if p_drive.lower() != result_drive.lower():
131 # Different drives => ignore the first path entirely
132 result_drive = p_drive
133 result_path = p_path
134 continue
135 # Same drive in different case
136 result_drive = p_drive
137 # Second path is relative to the first
138 if result_path and result_path[-1] not in seps:
139 result_path = result_path + sep
140 result_path = result_path + p_path
141 ## add separator between UNC and non-absolute path
142 if (result_path and result_path[0] not in seps and
143 result_drive and result_drive[-1:] != colon):
144 return result_drive + sep + result_path
145 return result_drive + result_path
146 except (TypeError, AttributeError, BytesWarning):
147 genericpath._check_arg_types('join', path, *paths)
148 raise
149
150
151 # Split a path in a drive specification (a drive letter followed by a
152 # colon) and the path specification.
153 # It is always true that drivespec + pathspec == p
154 def splitdrive(p):
155 """Split a pathname into drive/UNC sharepoint and relative path specifiers.
156 Returns a 2-tuple (drive_or_unc, path); either part may be empty.
157
158 If you assign
159 result = splitdrive(p)
160 It is always true that:
161 result[0] + result[1] == p
162
163 If the path contained a drive letter, drive_or_unc will contain everything
164 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")
165
166 If the path contained a UNC path, the drive_or_unc will contain the host name
167 and share up to but not including the fourth directory separator character.
168 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
169
170 Paths cannot contain both a drive letter and a UNC path.
171
172 """
173 p = os.fspath(p)
174 if len(p) >= 2:
175 if isinstance(p, bytes):
176 sep = b'\\'
177 altsep = b'/'
178 colon = b':'
179 unc_prefix = b'\\\\?\\UNC\\'
180 else:
181 sep = '\\'
182 altsep = '/'
183 colon = ':'
184 unc_prefix = '\\\\?\\UNC\\'
185 normp = p.replace(altsep, sep)
186 if normp[0:2] == sep * 2:
187 # UNC drives, e.g. \\server\share or \\?\UNC\server\share
188 # Device drives, e.g. \\.\device or \\?\device
189 start = 8 if normp[:8].upper() == unc_prefix else 2
190 index = normp.find(sep, start)
191 if index == -1:
192 return p, p[:0]
193 index2 = normp.find(sep, index + 1)
194 if index2 == -1:
195 return p, p[:0]
196 return p[:index2], p[index2:]
197 if normp[1:2] == colon:
198 # Drive-letter drives, e.g. X:
199 return p[:2], p[2:]
200 return p[:0], p
201
202
203 # Split a path in head (everything up to the last '/') and tail (the
204 # rest). After the trailing '/' is stripped, the invariant
205 # join(head, tail) == p holds.
206 # The resulting head won't end in '/' unless it is the root.
207
208 def split(p):
209 """Split a pathname.
210
211 Return tuple (head, tail) where tail is everything after the final slash.
212 Either part may be empty."""
213 p = os.fspath(p)
214 seps = _get_bothseps(p)
215 d, p = splitdrive(p)
216 # set i to index beyond p's last slash
217 i = len(p)
218 while i and p[i-1] not in seps:
219 i -= 1
220 head, tail = p[:i], p[i:] # now tail has no slashes
221 # remove trailing slashes from head, unless it's all slashes
222 head = head.rstrip(seps) or head
223 return d + head, tail
224
225
226 # Split a path in root and extension.
227 # The extension is everything starting at the last dot in the last
228 # pathname component; the root is everything before that.
229 # It is always true that root + ext == p.
230
231 def splitext(p):
232 p = os.fspath(p)
233 if isinstance(p, bytes):
234 return genericpath._splitext(p, b'\\', b'/', b'.')
235 else:
236 return genericpath._splitext(p, '\\', '/', '.')
237 splitext.__doc__ = genericpath._splitext.__doc__
238
239
240 # Return the tail (basename) part of a path.
241
242 def basename(p):
243 """Returns the final component of a pathname"""
244 return split(p)[1]
245
246
247 # Return the head (dirname) part of a path.
248
249 def dirname(p):
250 """Returns the directory component of a pathname"""
251 return split(p)[0]
252
253 # Is a path a symbolic link?
254 # This will always return false on systems where os.lstat doesn't exist.
255
256 def islink(path):
257 """Test whether a path is a symbolic link.
258 This will always return false for Windows prior to 6.0.
259 """
260 try:
261 st = os.lstat(path)
262 except (OSError, ValueError, AttributeError):
263 return False
264 return stat.S_ISLNK(st.st_mode)
265
266 # Being true for dangling symbolic links is also useful.
267
268 def lexists(path):
269 """Test whether a path exists. Returns True for broken symbolic links"""
270 try:
271 st = os.lstat(path)
272 except (OSError, ValueError):
273 return False
274 return True
275
276 # Is a path a mount point?
277 # Any drive letter root (eg c:\)
278 # Any share UNC (eg \\server\share)
279 # Any volume mounted on a filesystem folder
280 #
281 # No one method detects all three situations. Historically we've lexically
282 # detected drive letter roots and share UNCs. The canonical approach to
283 # detecting mounted volumes (querying the reparse tag) fails for the most
284 # common case: drive letter roots. The alternative which uses GetVolumePathName
285 # fails if the drive letter is the result of a SUBST.
286 try:
287 from nt import _getvolumepathname
288 except ImportError:
289 _getvolumepathname = None
290 def ismount(path):
291 """Test whether a path is a mount point (a drive root, the root of a
292 share, or a mounted volume)"""
293 path = os.fspath(path)
294 seps = _get_bothseps(path)
295 path = abspath(path)
296 root, rest = splitdrive(path)
297 if root and root[0] in seps:
298 return (not rest) or (rest in seps)
299 if rest and rest in seps:
300 return True
301
302 if _getvolumepathname:
303 x = path.rstrip(seps)
304 y =_getvolumepathname(path).rstrip(seps)
305 return x.casefold() == y.casefold()
306 else:
307 return False
308
309
310 # Expand paths beginning with '~' or '~user'.
311 # '~' means $HOME; '~user' means that user's home directory.
312 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
313 # the path is returned unchanged (leaving error reporting to whatever
314 # function is called with the expanded path as argument).
315 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
316 # (A function should also be defined to do full *sh-style environment
317 # variable expansion.)
318
319 def expanduser(path):
320 """Expand ~ and ~user constructs.
321
322 If user or $HOME is unknown, do nothing."""
323 path = os.fspath(path)
324 if isinstance(path, bytes):
325 tilde = b'~'
326 else:
327 tilde = '~'
328 if not path.startswith(tilde):
329 return path
330 i, n = 1, len(path)
331 while i < n and path[i] not in _get_bothseps(path):
332 i += 1
333
334 if 'USERPROFILE' in os.environ:
335 userhome = os.environ['USERPROFILE']
336 elif not 'HOMEPATH' in os.environ:
337 return path
338 else:
339 try:
340 drive = os.environ['HOMEDRIVE']
341 except KeyError:
342 drive = ''
343 userhome = join(drive, os.environ['HOMEPATH'])
344
345 if i != 1: #~user
346 target_user = path[1:i]
347 if isinstance(target_user, bytes):
348 target_user = os.fsdecode(target_user)
349 current_user = os.environ.get('USERNAME')
350
351 if target_user != current_user:
352 # Try to guess user home directory. By default all user
353 # profile directories are located in the same place and are
354 # named by corresponding usernames. If userhome isn't a
355 # normal profile directory, this guess is likely wrong,
356 # so we bail out.
357 if current_user != basename(userhome):
358 return path
359 userhome = join(dirname(userhome), target_user)
360
361 if isinstance(path, bytes):
362 userhome = os.fsencode(userhome)
363
364 return userhome + path[i:]
365
366
367 # Expand paths containing shell variable substitutions.
368 # The following rules apply:
369 # - no expansion within single quotes
370 # - '$$' is translated into '$'
371 # - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
372 # - ${varname} is accepted.
373 # - $varname is accepted.
374 # - %varname% is accepted.
375 # - varnames can be made out of letters, digits and the characters '_-'
376 # (though is not verified in the ${varname} and %varname% cases)
377 # XXX With COMMAND.COM you can use any characters in a variable name,
378 # XXX except '^|<>='.
379
380 def expandvars(path):
381 """Expand shell variables of the forms $var, ${var} and %var%.
382
383 Unknown variables are left unchanged."""
384 path = os.fspath(path)
385 if isinstance(path, bytes):
386 if b'$' not in path and b'%' not in path:
387 return path
388 import string
389 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
390 quote = b'\''
391 percent = b'%'
392 brace = b'{'
393 rbrace = b'}'
394 dollar = b'$'
395 environ = getattr(os, 'environb', None)
396 else:
397 if '$' not in path and '%' not in path:
398 return path
399 import string
400 varchars = string.ascii_letters + string.digits + '_-'
401 quote = '\''
402 percent = '%'
403 brace = '{'
404 rbrace = '}'
405 dollar = '$'
406 environ = os.environ
407 res = path[:0]
408 index = 0
409 pathlen = len(path)
410 while index < pathlen:
411 c = path[index:index+1]
412 if c == quote: # no expansion within single quotes
413 path = path[index + 1:]
414 pathlen = len(path)
415 try:
416 index = path.index(c)
417 res += c + path[:index + 1]
418 except ValueError:
419 res += c + path
420 index = pathlen - 1
421 elif c == percent: # variable or '%'
422 if path[index + 1:index + 2] == percent:
423 res += c
424 index += 1
425 else:
426 path = path[index+1:]
427 pathlen = len(path)
428 try:
429 index = path.index(percent)
430 except ValueError:
431 res += percent + path
432 index = pathlen - 1
433 else:
434 var = path[:index]
435 try:
436 if environ is None:
437 value = os.fsencode(os.environ[os.fsdecode(var)])
438 else:
439 value = environ[var]
440 except KeyError:
441 value = percent + var + percent
442 res += value
443 elif c == dollar: # variable or '$$'
444 if path[index + 1:index + 2] == dollar:
445 res += c
446 index += 1
447 elif path[index + 1:index + 2] == brace:
448 path = path[index+2:]
449 pathlen = len(path)
450 try:
451 index = path.index(rbrace)
452 except ValueError:
453 res += dollar + brace + path
454 index = pathlen - 1
455 else:
456 var = path[:index]
457 try:
458 if environ is None:
459 value = os.fsencode(os.environ[os.fsdecode(var)])
460 else:
461 value = environ[var]
462 except KeyError:
463 value = dollar + brace + var + rbrace
464 res += value
465 else:
466 var = path[:0]
467 index += 1
468 c = path[index:index + 1]
469 while c and c in varchars:
470 var += c
471 index += 1
472 c = path[index:index + 1]
473 try:
474 if environ is None:
475 value = os.fsencode(os.environ[os.fsdecode(var)])
476 else:
477 value = environ[var]
478 except KeyError:
479 value = dollar + var
480 res += value
481 if c:
482 index -= 1
483 else:
484 res += c
485 index += 1
486 return res
487
488
489 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
490 # Previously, this function also truncated pathnames to 8+3 format,
491 # but as this module is called "ntpath", that's obviously wrong!
492 try:
493 from nt import _path_normpath
494
495 except ImportError:
496 def normpath(path):
497 """Normalize path, eliminating double slashes, etc."""
498 path = os.fspath(path)
499 if isinstance(path, bytes):
500 sep = b'\\'
501 altsep = b'/'
502 curdir = b'.'
503 pardir = b'..'
504 else:
505 sep = '\\'
506 altsep = '/'
507 curdir = '.'
508 pardir = '..'
509 path = path.replace(altsep, sep)
510 prefix, path = splitdrive(path)
511
512 # collapse initial backslashes
513 if path.startswith(sep):
514 prefix += sep
515 path = path.lstrip(sep)
516
517 comps = path.split(sep)
518 i = 0
519 while i < len(comps):
520 if not comps[i] or comps[i] == curdir:
521 del comps[i]
522 elif comps[i] == pardir:
523 if i > 0 and comps[i-1] != pardir:
524 del comps[i-1:i+1]
525 i -= 1
526 elif i == 0 and prefix.endswith(sep):
527 del comps[i]
528 else:
529 i += 1
530 else:
531 i += 1
532 # If the path is now empty, substitute '.'
533 if not prefix and not comps:
534 comps.append(curdir)
535 return prefix + sep.join(comps)
536
537 else:
538 def normpath(path):
539 """Normalize path, eliminating double slashes, etc."""
540 path = os.fspath(path)
541 if isinstance(path, bytes):
542 return os.fsencode(_path_normpath(os.fsdecode(path))) or b"."
543 return _path_normpath(path) or "."
544
545
546 def _abspath_fallback(path):
547 """Return the absolute version of a path as a fallback function in case
548 `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
549 more.
550
551 """
552
553 path = os.fspath(path)
554 if not isabs(path):
555 if isinstance(path, bytes):
556 cwd = os.getcwdb()
557 else:
558 cwd = os.getcwd()
559 path = join(cwd, path)
560 return normpath(path)
561
562 # Return an absolute path.
563 try:
564 from nt import _getfullpathname
565
566 except ImportError: # not running on Windows - mock up something sensible
567 abspath = _abspath_fallback
568
569 else: # use native Windows method on Windows
570 def abspath(path):
571 """Return the absolute version of a path."""
572 try:
573 return _getfullpathname(normpath(path))
574 except (OSError, ValueError):
575 return _abspath_fallback(path)
576
577 try:
578 from nt import _getfinalpathname, readlink as _nt_readlink
579 except ImportError:
580 # realpath is a no-op on systems without _getfinalpathname support.
581 realpath = abspath
582 else:
583 def _readlink_deep(path):
584 # These error codes indicate that we should stop reading links and
585 # return the path we currently have.
586 # 1: ERROR_INVALID_FUNCTION
587 # 2: ERROR_FILE_NOT_FOUND
588 # 3: ERROR_DIRECTORY_NOT_FOUND
589 # 5: ERROR_ACCESS_DENIED
590 # 21: ERROR_NOT_READY (implies drive with no media)
591 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
592 # 50: ERROR_NOT_SUPPORTED (implies no support for reparse points)
593 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
594 # 87: ERROR_INVALID_PARAMETER
595 # 4390: ERROR_NOT_A_REPARSE_POINT
596 # 4392: ERROR_INVALID_REPARSE_DATA
597 # 4393: ERROR_REPARSE_TAG_INVALID
598 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 67, 87, 4390, 4392, 4393
599
600 seen = set()
601 while normcase(path) not in seen:
602 seen.add(normcase(path))
603 try:
604 old_path = path
605 path = _nt_readlink(path)
606 # Links may be relative, so resolve them against their
607 # own location
608 if not isabs(path):
609 # If it's something other than a symlink, we don't know
610 # what it's actually going to be resolved against, so
611 # just return the old path.
612 if not islink(old_path):
613 path = old_path
614 break
615 path = normpath(join(dirname(old_path), path))
616 except OSError as ex:
617 if ex.winerror in allowed_winerror:
618 break
619 raise
620 except ValueError:
621 # Stop on reparse points that are not symlinks
622 break
623 return path
624
625 def _getfinalpathname_nonstrict(path):
626 # These error codes indicate that we should stop resolving the path
627 # and return the value we currently have.
628 # 1: ERROR_INVALID_FUNCTION
629 # 2: ERROR_FILE_NOT_FOUND
630 # 3: ERROR_DIRECTORY_NOT_FOUND
631 # 5: ERROR_ACCESS_DENIED
632 # 21: ERROR_NOT_READY (implies drive with no media)
633 # 32: ERROR_SHARING_VIOLATION (probably an NTFS paging file)
634 # 50: ERROR_NOT_SUPPORTED
635 # 53: ERROR_BAD_NETPATH
636 # 65: ERROR_NETWORK_ACCESS_DENIED
637 # 67: ERROR_BAD_NET_NAME (implies remote server unavailable)
638 # 87: ERROR_INVALID_PARAMETER
639 # 123: ERROR_INVALID_NAME
640 # 161: ERROR_BAD_PATHNAME
641 # 1920: ERROR_CANT_ACCESS_FILE
642 # 1921: ERROR_CANT_RESOLVE_FILENAME (implies unfollowable symlink)
643 allowed_winerror = 1, 2, 3, 5, 21, 32, 50, 53, 65, 67, 87, 123, 161, 1920, 1921
644
645 # Non-strict algorithm is to find as much of the target directory
646 # as we can and join the rest.
647 tail = path[:0]
648 while path:
649 try:
650 path = _getfinalpathname(path)
651 return join(path, tail) if tail else path
652 except OSError as ex:
653 if ex.winerror not in allowed_winerror:
654 raise
655 try:
656 # The OS could not resolve this path fully, so we attempt
657 # to follow the link ourselves. If we succeed, join the tail
658 # and return.
659 new_path = _readlink_deep(path)
660 if new_path != path:
661 return join(new_path, tail) if tail else new_path
662 except OSError:
663 # If we fail to readlink(), let's keep traversing
664 pass
665 path, name = split(path)
666 # TODO (bpo-38186): Request the real file name from the directory
667 # entry using FindFirstFileW. For now, we will return the path
668 # as best we have it
669 if path and not name:
670 return path + tail
671 tail = join(name, tail) if tail else name
672 return tail
673
674 def realpath(path, *, strict=False):
675 path = normpath(path)
676 if isinstance(path, bytes):
677 prefix = b'\\\\?\\'
678 unc_prefix = b'\\\\?\\UNC\\'
679 new_unc_prefix = b'\\\\'
680 cwd = os.getcwdb()
681 # bpo-38081: Special case for realpath(b'nul')
682 if normcase(path) == normcase(os.fsencode(devnull)):
683 return b'\\\\.\\NUL'
684 else:
685 prefix = '\\\\?\\'
686 unc_prefix = '\\\\?\\UNC\\'
687 new_unc_prefix = '\\\\'
688 cwd = os.getcwd()
689 # bpo-38081: Special case for realpath('nul')
690 if normcase(path) == normcase(devnull):
691 return '\\\\.\\NUL'
692 had_prefix = path.startswith(prefix)
693 if not had_prefix and not isabs(path):
694 path = join(cwd, path)
695 try:
696 path = _getfinalpathname(path)
697 initial_winerror = 0
698 except ValueError as ex:
699 # gh-106242: Raised for embedded null characters
700 # In strict mode, we convert into an OSError.
701 # Non-strict mode returns the path as-is, since we've already
702 # made it absolute.
703 if strict:
704 raise OSError(str(ex)) from None
705 path = normpath(path)
706 except OSError as ex:
707 if strict:
708 raise
709 initial_winerror = ex.winerror
710 path = _getfinalpathname_nonstrict(path)
711 # The path returned by _getfinalpathname will always start with \\?\ -
712 # strip off that prefix unless it was already provided on the original
713 # path.
714 if not had_prefix and path.startswith(prefix):
715 # For UNC paths, the prefix will actually be \\?\UNC\
716 # Handle that case as well.
717 if path.startswith(unc_prefix):
718 spath = new_unc_prefix + path[len(unc_prefix):]
719 else:
720 spath = path[len(prefix):]
721 # Ensure that the non-prefixed path resolves to the same path
722 try:
723 if _getfinalpathname(spath) == path:
724 path = spath
725 except ValueError as ex:
726 # Unexpected, as an invalid path should not have gained a prefix
727 # at any point, but we ignore this error just in case.
728 pass
729 except OSError as ex:
730 # If the path does not exist and originally did not exist, then
731 # strip the prefix anyway.
732 if ex.winerror == initial_winerror:
733 path = spath
734 return path
735
736
737 # Win9x family and earlier have no Unicode filename support.
738 supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
739 sys.getwindowsversion()[3] >= 2)
740
741 def relpath(path, start=None):
742 """Return a relative version of a path"""
743 path = os.fspath(path)
744 if isinstance(path, bytes):
745 sep = b'\\'
746 curdir = b'.'
747 pardir = b'..'
748 else:
749 sep = '\\'
750 curdir = '.'
751 pardir = '..'
752
753 if start is None:
754 start = curdir
755
756 if not path:
757 raise ValueError("no path specified")
758
759 start = os.fspath(start)
760 try:
761 start_abs = abspath(normpath(start))
762 path_abs = abspath(normpath(path))
763 start_drive, start_rest = splitdrive(start_abs)
764 path_drive, path_rest = splitdrive(path_abs)
765 if normcase(start_drive) != normcase(path_drive):
766 raise ValueError("path is on mount %r, start on mount %r" % (
767 path_drive, start_drive))
768
769 start_list = [x for x in start_rest.split(sep) if x]
770 path_list = [x for x in path_rest.split(sep) if x]
771 # Work out how much of the filepath is shared by start and path.
772 i = 0
773 for e1, e2 in zip(start_list, path_list):
774 if normcase(e1) != normcase(e2):
775 break
776 i += 1
777
778 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
779 if not rel_list:
780 return curdir
781 return join(*rel_list)
782 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning):
783 genericpath._check_arg_types('relpath', path, start)
784 raise
785
786
787 # Return the longest common sub-path of the sequence of paths given as input.
788 # The function is case-insensitive and 'separator-insensitive', i.e. if the
789 # only difference between two paths is the use of '\' versus '/' as separator,
790 # they are deemed to be equal.
791 #
792 # However, the returned path will have the standard '\' separator (even if the
793 # given paths had the alternative '/' separator) and will have the case of the
794 # first path given in the sequence. Additionally, any trailing separator is
795 # stripped from the returned path.
796
797 def commonpath(paths):
798 """Given a sequence of path names, returns the longest common sub-path."""
799
800 if not paths:
801 raise ValueError('commonpath() arg is an empty sequence')
802
803 paths = tuple(map(os.fspath, paths))
804 if isinstance(paths[0], bytes):
805 sep = b'\\'
806 altsep = b'/'
807 curdir = b'.'
808 else:
809 sep = '\\'
810 altsep = '/'
811 curdir = '.'
812
813 try:
814 drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths]
815 split_paths = [p.split(sep) for d, p in drivesplits]
816
817 try:
818 isabs, = set(p[:1] == sep for d, p in drivesplits)
819 except ValueError:
820 raise ValueError("Can't mix absolute and relative paths") from None
821
822 # Check that all drive letters or UNC paths match. The check is made only
823 # now otherwise type errors for mixing strings and bytes would not be
824 # caught.
825 if len(set(d for d, p in drivesplits)) != 1:
826 raise ValueError("Paths don't have the same drive")
827
828 drive, path = splitdrive(paths[0].replace(altsep, sep))
829 common = path.split(sep)
830 common = [c for c in common if c and c != curdir]
831
832 split_paths = [[c for c in s if c and c != curdir] for s in split_paths]
833 s1 = min(split_paths)
834 s2 = max(split_paths)
835 for i, c in enumerate(s1):
836 if c != s2[i]:
837 common = common[:i]
838 break
839 else:
840 common = common[:len(s1)]
841
842 prefix = drive + sep if isabs else drive
843 return prefix + sep.join(common)
844 except (TypeError, AttributeError):
845 genericpath._check_arg_types('commonpath', *paths)
846 raise
847
848
849 try:
850 # The genericpath.isdir implementation uses os.stat and checks the mode
851 # attribute to tell whether or not the path is a directory.
852 # This is overkill on Windows - just pass the path to GetFileAttributes
853 # and check the attribute from there.
854 from nt import _isdir as isdir
855 except ImportError:
856 # Use genericpath.isdir as imported above.
857 pass