1# Module 'ntpath' -- common operations on WinNT/Win95 pathnames 2"""Common pathname manipulations, WindowsNT/95 version. 3 4Instead of importing this module directly, import os and refer to this 5module as os.path. 6""" 7 8import os 9import sys 10import stat 11import genericpath 12from genericpath import * 13 14__all__ = ["normcase","isabs","join","splitdrive","split","splitext", 15 "basename","dirname","commonprefix","getsize","getmtime", 16 "getatime","getctime", "islink","exists","lexists","isdir","isfile", 17 "ismount", "expanduser","expandvars","normpath","abspath", 18 "splitunc","curdir","pardir","sep","pathsep","defpath","altsep", 19 "extsep","devnull","realpath","supports_unicode_filenames","relpath", 20 "samefile", "sameopenfile", "samestat", "commonpath"] 21 22# strings representing various path-related bits and pieces 23# These are primarily for export; internally, they are hardcoded. 24curdir = '.' 25pardir = '..' 26extsep = '.' 27sep = '\\' 28pathsep = ';' 29altsep = '/' 30defpath = '.;C:\\bin' 31devnull = 'nul' 32 33def _get_bothseps(path): 34 if isinstance(path, bytes): 35 return b'\\/' 36 else: 37 return '\\/' 38 39# Normalize the case of a pathname and map slashes to backslashes. 40# Other normalizations (such as optimizing '../' away) are not done 41# (this is done by normpath). 42 43def normcase(s): 44 """Normalize case of pathname. 45 46 Makes all characters lowercase and all slashes into backslashes.""" 47 s = os.fspath(s) 48 try: 49 if isinstance(s, bytes): 50 return s.replace(b'/', b'\\').lower() 51 else: 52 return s.replace('/', '\\').lower() 53 except (TypeError, AttributeError): 54 if not isinstance(s, (bytes, str)): 55 raise TypeError("normcase() argument must be str or bytes, " 56 "not %r" % s.__class__.__name__) from None 57 raise 58 59 60# Return whether a path is absolute. 61# Trivial in Posix, harder on Windows. 62# For Windows it is absolute if it starts with a slash or backslash (current 63# volume), or if a pathname after the volume-letter-and-colon or UNC-resource 64# starts with a slash or backslash. 65 66def isabs(s): 67 """Test whether a path is absolute""" 68 s = os.fspath(s) 69 s = splitdrive(s)[1] 70 return len(s) > 0 and s[0] in _get_bothseps(s) 71 72 73# Join two (or more) paths. 74def join(path, *paths): 75 path = os.fspath(path) 76 if isinstance(path, bytes): 77 sep = b'\\' 78 seps = b'\\/' 79 colon = b':' 80 else: 81 sep = '\\' 82 seps = '\\/' 83 colon = ':' 84 try: 85 if not paths: 86 path[:0] + sep #23780: Ensure compatible data type even if p is null. 87 result_drive, result_path = splitdrive(path) 88 for p in map(os.fspath, paths): 89 p_drive, p_path = splitdrive(p) 90 if p_path and p_path[0] in seps: 91 # Second path is absolute 92 if p_drive or not result_drive: 93 result_drive = p_drive 94 result_path = p_path 95 continue 96 elif p_drive and p_drive != result_drive: 97 if p_drive.lower() != result_drive.lower(): 98 # Different drives => ignore the first path entirely 99 result_drive = p_drive 100 result_path = p_path 101 continue 102 # Same drive in different case 103 result_drive = p_drive 104 # Second path is relative to the first 105 if result_path and result_path[-1] not in seps: 106 result_path = result_path + sep 107 result_path = result_path + p_path 108 ## add separator between UNC and non-absolute path 109 if (result_path and result_path[0] not in seps and 110 result_drive and result_drive[-1:] != colon): 111 return result_drive + sep + result_path 112 return result_drive + result_path 113 except (TypeError, AttributeError, BytesWarning): 114 genericpath._check_arg_types('join', path, *paths) 115 raise 116 117 118# Split a path in a drive specification (a drive letter followed by a 119# colon) and the path specification. 120# It is always true that drivespec + pathspec == p 121def splitdrive(p): 122 """Split a pathname into drive/UNC sharepoint and relative path specifiers. 123 Returns a 2-tuple (drive_or_unc, path); either part may be empty. 124 125 If you assign 126 result = splitdrive(p) 127 It is always true that: 128 result[0] + result[1] == p 129 130 If the path contained a drive letter, drive_or_unc will contain everything 131 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir") 132 133 If the path contained a UNC path, the drive_or_unc will contain the host name 134 and share up to but not including the fourth directory separator character. 135 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir") 136 137 Paths cannot contain both a drive letter and a UNC path. 138 139 """ 140 p = os.fspath(p) 141 if len(p) >= 2: 142 if isinstance(p, bytes): 143 sep = b'\\' 144 altsep = b'/' 145 colon = b':' 146 else: 147 sep = '\\' 148 altsep = '/' 149 colon = ':' 150 normp = p.replace(altsep, sep) 151 if (normp[0:2] == sep*2) and (normp[2:3] != sep): 152 # is a UNC path: 153 # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path 154 # \\machine\mountpoint\directory\etc\... 155 # directory ^^^^^^^^^^^^^^^ 156 index = normp.find(sep, 2) 157 if index == -1: 158 return p[:0], p 159 index2 = normp.find(sep, index + 1) 160 # a UNC path can't have two slashes in a row 161 # (after the initial two) 162 if index2 == index + 1: 163 return p[:0], p 164 if index2 == -1: 165 index2 = len(p) 166 return p[:index2], p[index2:] 167 if normp[1:2] == colon: 168 return p[:2], p[2:] 169 return p[:0], p 170 171 172# Parse UNC paths 173def splitunc(p): 174 """Deprecated since Python 3.1. Please use splitdrive() instead; 175 it now handles UNC paths. 176 177 Split a pathname into UNC mount point and relative path specifiers. 178 179 Return a 2-tuple (unc, rest); either part may be empty. 180 If unc is not empty, it has the form '//host/mount' (or similar 181 using backslashes). unc+rest is always the input path. 182 Paths containing drive letters never have a UNC part. 183 """ 184 import warnings 185 warnings.warn("ntpath.splitunc is deprecated, use ntpath.splitdrive instead", 186 DeprecationWarning, 2) 187 drive, path = splitdrive(p) 188 if len(drive) == 2: 189 # Drive letter present 190 return p[:0], p 191 return drive, path 192 193 194# Split a path in head (everything up to the last '/') and tail (the 195# rest). After the trailing '/' is stripped, the invariant 196# join(head, tail) == p holds. 197# The resulting head won't end in '/' unless it is the root. 198 199def split(p): 200 """Split a pathname. 201 202 Return tuple (head, tail) where tail is everything after the final slash. 203 Either part may be empty.""" 204 p = os.fspath(p) 205 seps = _get_bothseps(p) 206 d, p = splitdrive(p) 207 # set i to index beyond p's last slash 208 i = len(p) 209 while i and p[i-1] not in seps: 210 i -= 1 211 head, tail = p[:i], p[i:] # now tail has no slashes 212 # remove trailing slashes from head, unless it's all slashes 213 head = head.rstrip(seps) or head 214 return d + head, tail 215 216 217# Split a path in root and extension. 218# The extension is everything starting at the last dot in the last 219# pathname component; the root is everything before that. 220# It is always true that root + ext == p. 221 222def splitext(p): 223 p = os.fspath(p) 224 if isinstance(p, bytes): 225 return genericpath._splitext(p, b'\\', b'/', b'.') 226 else: 227 return genericpath._splitext(p, '\\', '/', '.') 228splitext.__doc__ = genericpath._splitext.__doc__ 229 230 231# Return the tail (basename) part of a path. 232 233def basename(p): 234 """Returns the final component of a pathname""" 235 return split(p)[1] 236 237 238# Return the head (dirname) part of a path. 239 240def dirname(p): 241 """Returns the directory component of a pathname""" 242 return split(p)[0] 243 244# Is a path a symbolic link? 245# This will always return false on systems where os.lstat doesn't exist. 246 247def islink(path): 248 """Test whether a path is a symbolic link. 249 This will always return false for Windows prior to 6.0. 250 """ 251 try: 252 st = os.lstat(path) 253 except (OSError, AttributeError): 254 return False 255 return stat.S_ISLNK(st.st_mode) 256 257# Being true for dangling symbolic links is also useful. 258 259def lexists(path): 260 """Test whether a path exists. Returns True for broken symbolic links""" 261 try: 262 st = os.lstat(path) 263 except OSError: 264 return False 265 return True 266 267# Is a path a mount point? 268# Any drive letter root (eg c:\) 269# Any share UNC (eg \\server\share) 270# Any volume mounted on a filesystem folder 271# 272# No one method detects all three situations. Historically we've lexically 273# detected drive letter roots and share UNCs. The canonical approach to 274# detecting mounted volumes (querying the reparse tag) fails for the most 275# common case: drive letter roots. The alternative which uses GetVolumePathName 276# fails if the drive letter is the result of a SUBST. 277try: 278 from nt import _getvolumepathname 279except ImportError: 280 _getvolumepathname = None 281def ismount(path): 282 """Test whether a path is a mount point (a drive root, the root of a 283 share, or a mounted volume)""" 284 path = os.fspath(path) 285 seps = _get_bothseps(path) 286 path = abspath(path) 287 root, rest = splitdrive(path) 288 if root and root[0] in seps: 289 return (not rest) or (rest in seps) 290 if rest in seps: 291 return True 292 293 if _getvolumepathname: 294 return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps) 295 else: 296 return False 297 298 299# Expand paths beginning with '~' or '~user'. 300# '~' means $HOME; '~user' means that user's home directory. 301# If the path doesn't begin with '~', or if the user or $HOME is unknown, 302# the path is returned unchanged (leaving error reporting to whatever 303# function is called with the expanded path as argument). 304# See also module 'glob' for expansion of *, ? and [...] in pathnames. 305# (A function should also be defined to do full *sh-style environment 306# variable expansion.) 307 308def expanduser(path): 309 """Expand ~ and ~user constructs. 310 311 If user or $HOME is unknown, do nothing.""" 312 path = os.fspath(path) 313 if isinstance(path, bytes): 314 tilde = b'~' 315 else: 316 tilde = '~' 317 if not path.startswith(tilde): 318 return path 319 i, n = 1, len(path) 320 while i < n and path[i] not in _get_bothseps(path): 321 i += 1 322 323 if 'HOME' in os.environ: 324 userhome = os.environ['HOME'] 325 elif 'USERPROFILE' in os.environ: 326 userhome = os.environ['USERPROFILE'] 327 elif not 'HOMEPATH' in os.environ: 328 return path 329 else: 330 try: 331 drive = os.environ['HOMEDRIVE'] 332 except KeyError: 333 drive = '' 334 userhome = join(drive, os.environ['HOMEPATH']) 335 336 if isinstance(path, bytes): 337 userhome = os.fsencode(userhome) 338 339 if i != 1: #~user 340 userhome = join(dirname(userhome), path[1:i]) 341 342 return userhome + path[i:] 343 344 345# Expand paths containing shell variable substitutions. 346# The following rules apply: 347# - no expansion within single quotes 348# - '$$' is translated into '$' 349# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% 350# - ${varname} is accepted. 351# - $varname is accepted. 352# - %varname% is accepted. 353# - varnames can be made out of letters, digits and the characters '_-' 354# (though is not verified in the ${varname} and %varname% cases) 355# XXX With COMMAND.COM you can use any characters in a variable name, 356# XXX except '^|<>='. 357 358def expandvars(path): 359 """Expand shell variables of the forms $var, ${var} and %var%. 360 361 Unknown variables are left unchanged.""" 362 path = os.fspath(path) 363 if isinstance(path, bytes): 364 if b'$' not in path and b'%' not in path: 365 return path 366 import string 367 varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii') 368 quote = b'\'' 369 percent = b'%' 370 brace = b'{' 371 rbrace = b'}' 372 dollar = b'$' 373 environ = getattr(os, 'environb', None) 374 else: 375 if '$' not in path and '%' not in path: 376 return path 377 import string 378 varchars = string.ascii_letters + string.digits + '_-' 379 quote = '\'' 380 percent = '%' 381 brace = '{' 382 rbrace = '}' 383 dollar = '$' 384 environ = os.environ 385 res = path[:0] 386 index = 0 387 pathlen = len(path) 388 while index < pathlen: 389 c = path[index:index+1] 390 if c == quote: # no expansion within single quotes 391 path = path[index + 1:] 392 pathlen = len(path) 393 try: 394 index = path.index(c) 395 res += c + path[:index + 1] 396 except ValueError: 397 res += c + path 398 index = pathlen - 1 399 elif c == percent: # variable or '%' 400 if path[index + 1:index + 2] == percent: 401 res += c 402 index += 1 403 else: 404 path = path[index+1:] 405 pathlen = len(path) 406 try: 407 index = path.index(percent) 408 except ValueError: 409 res += percent + path 410 index = pathlen - 1 411 else: 412 var = path[:index] 413 try: 414 if environ is None: 415 value = os.fsencode(os.environ[os.fsdecode(var)]) 416 else: 417 value = environ[var] 418 except KeyError: 419 value = percent + var + percent 420 res += value 421 elif c == dollar: # variable or '$$' 422 if path[index + 1:index + 2] == dollar: 423 res += c 424 index += 1 425 elif path[index + 1:index + 2] == brace: 426 path = path[index+2:] 427 pathlen = len(path) 428 try: 429 index = path.index(rbrace) 430 except ValueError: 431 res += dollar + brace + 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 = dollar + brace + var + rbrace 442 res += value 443 else: 444 var = path[:0] 445 index += 1 446 c = path[index:index + 1] 447 while c and c in varchars: 448 var += c 449 index += 1 450 c = path[index:index + 1] 451 try: 452 if environ is None: 453 value = os.fsencode(os.environ[os.fsdecode(var)]) 454 else: 455 value = environ[var] 456 except KeyError: 457 value = dollar + var 458 res += value 459 if c: 460 index -= 1 461 else: 462 res += c 463 index += 1 464 return res 465 466 467# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. 468# Previously, this function also truncated pathnames to 8+3 format, 469# but as this module is called "ntpath", that's obviously wrong! 470 471def normpath(path): 472 """Normalize path, eliminating double slashes, etc.""" 473 path = os.fspath(path) 474 if isinstance(path, bytes): 475 sep = b'\\' 476 altsep = b'/' 477 curdir = b'.' 478 pardir = b'..' 479 special_prefixes = (b'\\\\.\\', b'\\\\?\\') 480 else: 481 sep = '\\' 482 altsep = '/' 483 curdir = '.' 484 pardir = '..' 485 special_prefixes = ('\\\\.\\', '\\\\?\\') 486 if path.startswith(special_prefixes): 487 # in the case of paths with these prefixes: 488 # \\.\ -> device names 489 # \\?\ -> literal paths 490 # do not do any normalization, but return the path unchanged 491 return path 492 path = path.replace(altsep, sep) 493 prefix, path = splitdrive(path) 494 495 # collapse initial backslashes 496 if path.startswith(sep): 497 prefix += sep 498 path = path.lstrip(sep) 499 500 comps = path.split(sep) 501 i = 0 502 while i < len(comps): 503 if not comps[i] or comps[i] == curdir: 504 del comps[i] 505 elif comps[i] == pardir: 506 if i > 0 and comps[i-1] != pardir: 507 del comps[i-1:i+1] 508 i -= 1 509 elif i == 0 and prefix.endswith(sep): 510 del comps[i] 511 else: 512 i += 1 513 else: 514 i += 1 515 # If the path is now empty, substitute '.' 516 if not prefix and not comps: 517 comps.append(curdir) 518 return prefix + sep.join(comps) 519 520 521# Return an absolute path. 522try: 523 from nt import _getfullpathname 524 525except ImportError: # not running on Windows - mock up something sensible 526 def abspath(path): 527 """Return the absolute version of a path.""" 528 path = os.fspath(path) 529 if not isabs(path): 530 if isinstance(path, bytes): 531 cwd = os.getcwdb() 532 else: 533 cwd = os.getcwd() 534 path = join(cwd, path) 535 return normpath(path) 536 537else: # use native Windows method on Windows 538 def abspath(path): 539 """Return the absolute version of a path.""" 540 541 if path: # Empty path must return current working directory. 542 path = os.fspath(path) 543 try: 544 path = _getfullpathname(path) 545 except OSError: 546 pass # Bad path - return unchanged. 547 elif isinstance(path, bytes): 548 path = os.getcwdb() 549 else: 550 path = os.getcwd() 551 return normpath(path) 552 553# realpath is a no-op on systems without islink support 554realpath = abspath 555# Win9x family and earlier have no Unicode filename support. 556supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and 557 sys.getwindowsversion()[3] >= 2) 558 559def relpath(path, start=None): 560 """Return a relative version of a path""" 561 path = os.fspath(path) 562 if isinstance(path, bytes): 563 sep = b'\\' 564 curdir = b'.' 565 pardir = b'..' 566 else: 567 sep = '\\' 568 curdir = '.' 569 pardir = '..' 570 571 if start is None: 572 start = curdir 573 574 if not path: 575 raise ValueError("no path specified") 576 577 start = os.fspath(start) 578 try: 579 start_abs = abspath(normpath(start)) 580 path_abs = abspath(normpath(path)) 581 start_drive, start_rest = splitdrive(start_abs) 582 path_drive, path_rest = splitdrive(path_abs) 583 if normcase(start_drive) != normcase(path_drive): 584 raise ValueError("path is on mount %r, start on mount %r" % ( 585 path_drive, start_drive)) 586 587 start_list = [x for x in start_rest.split(sep) if x] 588 path_list = [x for x in path_rest.split(sep) if x] 589 # Work out how much of the filepath is shared by start and path. 590 i = 0 591 for e1, e2 in zip(start_list, path_list): 592 if normcase(e1) != normcase(e2): 593 break 594 i += 1 595 596 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 597 if not rel_list: 598 return curdir 599 return join(*rel_list) 600 except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning): 601 genericpath._check_arg_types('relpath', path, start) 602 raise 603 604 605# Return the longest common sub-path of the sequence of paths given as input. 606# The function is case-insensitive and 'separator-insensitive', i.e. if the 607# only difference between two paths is the use of '\' versus '/' as separator, 608# they are deemed to be equal. 609# 610# However, the returned path will have the standard '\' separator (even if the 611# given paths had the alternative '/' separator) and will have the case of the 612# first path given in the sequence. Additionally, any trailing separator is 613# stripped from the returned path. 614 615def commonpath(paths): 616 """Given a sequence of path names, returns the longest common sub-path.""" 617 618 if not paths: 619 raise ValueError('commonpath() arg is an empty sequence') 620 621 paths = tuple(map(os.fspath, paths)) 622 if isinstance(paths[0], bytes): 623 sep = b'\\' 624 altsep = b'/' 625 curdir = b'.' 626 else: 627 sep = '\\' 628 altsep = '/' 629 curdir = '.' 630 631 try: 632 drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths] 633 split_paths = [p.split(sep) for d, p in drivesplits] 634 635 try: 636 isabs, = set(p[:1] == sep for d, p in drivesplits) 637 except ValueError: 638 raise ValueError("Can't mix absolute and relative paths") from None 639 640 # Check that all drive letters or UNC paths match. The check is made only 641 # now otherwise type errors for mixing strings and bytes would not be 642 # caught. 643 if len(set(d for d, p in drivesplits)) != 1: 644 raise ValueError("Paths don't have the same drive") 645 646 drive, path = splitdrive(paths[0].replace(altsep, sep)) 647 common = path.split(sep) 648 common = [c for c in common if c and c != curdir] 649 650 split_paths = [[c for c in s if c and c != curdir] for s in split_paths] 651 s1 = min(split_paths) 652 s2 = max(split_paths) 653 for i, c in enumerate(s1): 654 if c != s2[i]: 655 common = common[:i] 656 break 657 else: 658 common = common[:len(s1)] 659 660 prefix = drive + sep if isabs else drive 661 return prefix + sep.join(common) 662 except (TypeError, AttributeError): 663 genericpath._check_arg_types('commonpath', *paths) 664 raise 665 666 667# determine if two files are in fact the same file 668try: 669 # GetFinalPathNameByHandle is available starting with Windows 6.0. 670 # Windows XP and non-Windows OS'es will mock _getfinalpathname. 671 if sys.getwindowsversion()[:2] >= (6, 0): 672 from nt import _getfinalpathname 673 else: 674 raise ImportError 675except (AttributeError, ImportError): 676 # On Windows XP and earlier, two files are the same if their absolute 677 # pathnames are the same. 678 # Non-Windows operating systems fake this method with an XP 679 # approximation. 680 def _getfinalpathname(f): 681 return normcase(abspath(f)) 682 683 684try: 685 # The genericpath.isdir implementation uses os.stat and checks the mode 686 # attribute to tell whether or not the path is a directory. 687 # This is overkill on Windows - just pass the path to GetFileAttributes 688 # and check the attribute from there. 689 from nt import _isdir as isdir 690except ImportError: 691 # Use genericpath.isdir as imported above. 692 pass 693