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 12import warnings 13 14from genericpath import * 15from genericpath import _unicode 16 17__all__ = ["normcase","isabs","join","splitdrive","split","splitext", 18 "basename","dirname","commonprefix","getsize","getmtime", 19 "getatime","getctime", "islink","exists","lexists","isdir","isfile", 20 "ismount","walk","expanduser","expandvars","normpath","abspath", 21 "splitunc","curdir","pardir","sep","pathsep","defpath","altsep", 22 "extsep","devnull","realpath","supports_unicode_filenames","relpath"] 23 24# strings representing various path-related bits and pieces 25curdir = '.' 26pardir = '..' 27extsep = '.' 28sep = '\\' 29pathsep = ';' 30altsep = '/' 31defpath = '.;C:\\bin' 32if 'ce' in sys.builtin_module_names: 33 defpath = '\\Windows' 34elif 'os2' in sys.builtin_module_names: 35 # OS/2 w/ VACPP 36 altsep = '/' 37devnull = 'nul' 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 return s.replace("/", "\\").lower() 48 49 50# Return whether a path is absolute. 51# Trivial in Posix, harder on the Mac or MS-DOS. 52# For DOS it is absolute if it starts with a slash or backslash (current 53# volume), or if a pathname after the volume letter and colon / UNC resource 54# starts with a slash or backslash. 55 56def isabs(s): 57 """Test whether a path is absolute""" 58 s = splitdrive(s)[1] 59 return s != '' and s[:1] in '/\\' 60 61 62# Join two (or more) paths. 63def join(path, *paths): 64 """Join two or more pathname components, inserting "\\" as needed.""" 65 result_drive, result_path = splitdrive(path) 66 for p in paths: 67 p_drive, p_path = splitdrive(p) 68 if p_path and p_path[0] in '\\/': 69 # Second path is absolute 70 if p_drive or not result_drive: 71 result_drive = p_drive 72 result_path = p_path 73 continue 74 elif p_drive and p_drive != result_drive: 75 if p_drive.lower() != result_drive.lower(): 76 # Different drives => ignore the first path entirely 77 result_drive = p_drive 78 result_path = p_path 79 continue 80 # Same drive in different case 81 result_drive = p_drive 82 # Second path is relative to the first 83 if result_path and result_path[-1] not in '\\/': 84 result_path = result_path + '\\' 85 result_path = result_path + p_path 86 ## add separator between UNC and non-absolute path 87 if (result_path and result_path[0] not in '\\/' and 88 result_drive and result_drive[-1:] != ':'): 89 return result_drive + sep + result_path 90 return result_drive + result_path 91 92 93# Split a path in a drive specification (a drive letter followed by a 94# colon) and the path specification. 95# It is always true that drivespec + pathspec == p 96def splitdrive(p): 97 """Split a pathname into drive/UNC sharepoint and relative path specifiers. 98 Returns a 2-tuple (drive_or_unc, path); either part may be empty. 99 100 If you assign 101 result = splitdrive(p) 102 It is always true that: 103 result[0] + result[1] == p 104 105 If the path contained a drive letter, drive_or_unc will contain everything 106 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir") 107 108 If the path contained a UNC path, the drive_or_unc will contain the host name 109 and share up to but not including the fourth directory separator character. 110 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir") 111 112 Paths cannot contain both a drive letter and a UNC path. 113 114 """ 115 if len(p) > 1: 116 normp = p.replace(altsep, sep) 117 if (normp[0:2] == sep*2) and (normp[2:3] != sep): 118 # is a UNC path: 119 # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path 120 # \\machine\mountpoint\directory\etc\... 121 # directory ^^^^^^^^^^^^^^^ 122 index = normp.find(sep, 2) 123 if index == -1: 124 return '', p 125 index2 = normp.find(sep, index + 1) 126 # a UNC path can't have two slashes in a row 127 # (after the initial two) 128 if index2 == index + 1: 129 return '', p 130 if index2 == -1: 131 index2 = len(p) 132 return p[:index2], p[index2:] 133 if normp[1] == ':': 134 return p[:2], p[2:] 135 return '', p 136 137# Parse UNC paths 138def splitunc(p): 139 """Split a pathname into UNC mount point and relative path specifiers. 140 141 Return a 2-tuple (unc, rest); either part may be empty. 142 If unc is not empty, it has the form '//host/mount' (or similar 143 using backslashes). unc+rest is always the input path. 144 Paths containing drive letters never have a UNC part. 145 """ 146 if p[1:2] == ':': 147 return '', p # Drive letter present 148 firstTwo = p[0:2] 149 if firstTwo == '//' or firstTwo == '\\\\': 150 # is a UNC path: 151 # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter 152 # \\machine\mountpoint\directories... 153 # directory ^^^^^^^^^^^^^^^ 154 normp = p.replace('\\', '/') 155 index = normp.find('/', 2) 156 if index <= 2: 157 return '', p 158 index2 = normp.find('/', index + 1) 159 # a UNC path can't have two slashes in a row 160 # (after the initial two) 161 if index2 == index + 1: 162 return '', p 163 if index2 == -1: 164 index2 = len(p) 165 return p[:index2], p[index2:] 166 return '', p 167 168 169# Split a path in head (everything up to the last '/') and tail (the 170# rest). After the trailing '/' is stripped, the invariant 171# join(head, tail) == p holds. 172# The resulting head won't end in '/' unless it is the root. 173 174def split(p): 175 """Split a pathname. 176 177 Return tuple (head, tail) where tail is everything after the final slash. 178 Either part may be empty.""" 179 180 d, p = splitdrive(p) 181 # set i to index beyond p's last slash 182 i = len(p) 183 while i and p[i-1] not in '/\\': 184 i = i - 1 185 head, tail = p[:i], p[i:] # now tail has no slashes 186 # remove trailing slashes from head, unless it's all slashes 187 head2 = head 188 while head2 and head2[-1] in '/\\': 189 head2 = head2[:-1] 190 head = head2 or head 191 return d + head, tail 192 193 194# Split a path in root and extension. 195# The extension is everything starting at the last dot in the last 196# pathname component; the root is everything before that. 197# It is always true that root + ext == p. 198 199def splitext(p): 200 return genericpath._splitext(p, sep, altsep, extsep) 201splitext.__doc__ = genericpath._splitext.__doc__ 202 203 204# Return the tail (basename) part of a path. 205 206def basename(p): 207 """Returns the final component of a pathname""" 208 return split(p)[1] 209 210 211# Return the head (dirname) part of a path. 212 213def dirname(p): 214 """Returns the directory component of a pathname""" 215 return split(p)[0] 216 217# Is a path a symbolic link? 218# This will always return false on systems where posix.lstat doesn't exist. 219 220def islink(path): 221 """Test for symbolic link. 222 On WindowsNT/95 and OS/2 always returns false 223 """ 224 return False 225 226# alias exists to lexists 227lexists = exists 228 229# Is a path a mount point? Either a root (with or without drive letter) 230# or a UNC path with at most a / or \ after the mount point. 231 232def ismount(path): 233 """Test whether a path is a mount point (defined as root of drive)""" 234 unc, rest = splitunc(path) 235 if unc: 236 return rest in ("", "/", "\\") 237 p = splitdrive(path)[1] 238 return len(p) == 1 and p[0] in '/\\' 239 240 241# Directory tree walk. 242# For each directory under top (including top itself, but excluding 243# '.' and '..'), func(arg, dirname, filenames) is called, where 244# dirname is the name of the directory and filenames is the list 245# of files (and subdirectories etc.) in the directory. 246# The func may modify the filenames list, to implement a filter, 247# or to impose a different order of visiting. 248 249def walk(top, func, arg): 250 """Directory tree walk with callback function. 251 252 For each directory in the directory tree rooted at top (including top 253 itself, but excluding '.' and '..'), call func(arg, dirname, fnames). 254 dirname is the name of the directory, and fnames a list of the names of 255 the files and subdirectories in dirname (excluding '.' and '..'). func 256 may modify the fnames list in-place (e.g. via del or slice assignment), 257 and walk will only recurse into the subdirectories whose names remain in 258 fnames; this can be used to implement a filter, or to impose a specific 259 order of visiting. No semantics are defined for, or required of, arg, 260 beyond that arg is always passed to func. It can be used, e.g., to pass 261 a filename pattern, or a mutable object designed to accumulate 262 statistics. Passing None for arg is common.""" 263 warnings.warnpy3k("In 3.x, os.path.walk is removed in favor of os.walk.", 264 stacklevel=2) 265 try: 266 names = os.listdir(top) 267 except os.error: 268 return 269 func(arg, top, names) 270 for name in names: 271 name = join(top, name) 272 if isdir(name): 273 walk(name, func, arg) 274 275 276# Expand paths beginning with '~' or '~user'. 277# '~' means $HOME; '~user' means that user's home directory. 278# If the path doesn't begin with '~', or if the user or $HOME is unknown, 279# the path is returned unchanged (leaving error reporting to whatever 280# function is called with the expanded path as argument). 281# See also module 'glob' for expansion of *, ? and [...] in pathnames. 282# (A function should also be defined to do full *sh-style environment 283# variable expansion.) 284 285def expanduser(path): 286 """Expand ~ and ~user constructs. 287 288 If user or $HOME is unknown, do nothing.""" 289 if path[:1] != '~': 290 return path 291 i, n = 1, len(path) 292 while i < n and path[i] not in '/\\': 293 i = i + 1 294 295 if 'HOME' in os.environ: 296 userhome = os.environ['HOME'] 297 elif 'USERPROFILE' in os.environ: 298 userhome = os.environ['USERPROFILE'] 299 elif not 'HOMEPATH' in os.environ: 300 return path 301 else: 302 try: 303 drive = os.environ['HOMEDRIVE'] 304 except KeyError: 305 drive = '' 306 userhome = join(drive, os.environ['HOMEPATH']) 307 308 if i != 1: #~user 309 userhome = join(dirname(userhome), path[1:i]) 310 311 return userhome + path[i:] 312 313 314# Expand paths containing shell variable substitutions. 315# The following rules apply: 316# - no expansion within single quotes 317# - '$$' is translated into '$' 318# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% 319# - ${varname} is accepted. 320# - $varname is accepted. 321# - %varname% is accepted. 322# - varnames can be made out of letters, digits and the characters '_-' 323# (though is not verified in the ${varname} and %varname% cases) 324# XXX With COMMAND.COM you can use any characters in a variable name, 325# XXX except '^|<>='. 326 327def expandvars(path): 328 """Expand shell variables of the forms $var, ${var} and %var%. 329 330 Unknown variables are left unchanged.""" 331 if '$' not in path and '%' not in path: 332 return path 333 import string 334 varchars = string.ascii_letters + string.digits + '_-' 335 if isinstance(path, _unicode): 336 encoding = sys.getfilesystemencoding() 337 def getenv(var): 338 return os.environ[var.encode(encoding)].decode(encoding) 339 else: 340 def getenv(var): 341 return os.environ[var] 342 res = '' 343 index = 0 344 pathlen = len(path) 345 while index < pathlen: 346 c = path[index] 347 if c == '\'': # no expansion within single quotes 348 path = path[index + 1:] 349 pathlen = len(path) 350 try: 351 index = path.index('\'') 352 res = res + '\'' + path[:index + 1] 353 except ValueError: 354 res = res + c + path 355 index = pathlen - 1 356 elif c == '%': # variable or '%' 357 if path[index + 1:index + 2] == '%': 358 res = res + c 359 index = index + 1 360 else: 361 path = path[index+1:] 362 pathlen = len(path) 363 try: 364 index = path.index('%') 365 except ValueError: 366 res = res + '%' + path 367 index = pathlen - 1 368 else: 369 var = path[:index] 370 try: 371 res = res + getenv(var) 372 except KeyError: 373 res = res + '%' + var + '%' 374 elif c == '$': # variable or '$$' 375 if path[index + 1:index + 2] == '$': 376 res = res + c 377 index = index + 1 378 elif path[index + 1:index + 2] == '{': 379 path = path[index+2:] 380 pathlen = len(path) 381 try: 382 index = path.index('}') 383 var = path[:index] 384 try: 385 res = res + getenv(var) 386 except KeyError: 387 res = res + '${' + var + '}' 388 except ValueError: 389 res = res + '${' + path 390 index = pathlen - 1 391 else: 392 var = '' 393 index = index + 1 394 c = path[index:index + 1] 395 while c != '' and c in varchars: 396 var = var + c 397 index = index + 1 398 c = path[index:index + 1] 399 try: 400 res = res + getenv(var) 401 except KeyError: 402 res = res + '$' + var 403 if c != '': 404 index = index - 1 405 else: 406 res = res + c 407 index = index + 1 408 return res 409 410 411# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. 412# Previously, this function also truncated pathnames to 8+3 format, 413# but as this module is called "ntpath", that's obviously wrong! 414 415def normpath(path): 416 """Normalize path, eliminating double slashes, etc.""" 417 # Preserve unicode (if path is unicode) 418 backslash, dot = (u'\\', u'.') if isinstance(path, _unicode) else ('\\', '.') 419 if path.startswith(('\\\\.\\', '\\\\?\\')): 420 # in the case of paths with these prefixes: 421 # \\.\ -> device names 422 # \\?\ -> literal paths 423 # do not do any normalization, but return the path unchanged 424 return path 425 path = path.replace("/", "\\") 426 prefix, path = splitdrive(path) 427 # We need to be careful here. If the prefix is empty, and the path starts 428 # with a backslash, it could either be an absolute path on the current 429 # drive (\dir1\dir2\file) or a UNC filename (\\server\mount\dir1\file). It 430 # is therefore imperative NOT to collapse multiple backslashes blindly in 431 # that case. 432 # The code below preserves multiple backslashes when there is no drive 433 # letter. This means that the invalid filename \\\a\b is preserved 434 # unchanged, where a\\\b is normalised to a\b. It's not clear that there 435 # is any better behaviour for such edge cases. 436 if prefix == '': 437 # No drive letter - preserve initial backslashes 438 while path[:1] == "\\": 439 prefix = prefix + backslash 440 path = path[1:] 441 else: 442 # We have a drive letter - collapse initial backslashes 443 if path.startswith("\\"): 444 prefix = prefix + backslash 445 path = path.lstrip("\\") 446 comps = path.split("\\") 447 i = 0 448 while i < len(comps): 449 if comps[i] in ('.', ''): 450 del comps[i] 451 elif comps[i] == '..': 452 if i > 0 and comps[i-1] != '..': 453 del comps[i-1:i+1] 454 i -= 1 455 elif i == 0 and prefix.endswith("\\"): 456 del comps[i] 457 else: 458 i += 1 459 else: 460 i += 1 461 # If the path is now empty, substitute '.' 462 if not prefix and not comps: 463 comps.append(dot) 464 return prefix + backslash.join(comps) 465 466 467# Return an absolute path. 468try: 469 from nt import _getfullpathname 470 471except ImportError: # not running on Windows - mock up something sensible 472 def abspath(path): 473 """Return the absolute version of a path.""" 474 if not isabs(path): 475 if isinstance(path, _unicode): 476 cwd = os.getcwdu() 477 else: 478 cwd = os.getcwd() 479 path = join(cwd, path) 480 return normpath(path) 481 482else: # use native Windows method on Windows 483 def abspath(path): 484 """Return the absolute version of a path.""" 485 486 if path: # Empty path must return current working directory. 487 try: 488 path = _getfullpathname(path) 489 except WindowsError: 490 pass # Bad path - return unchanged. 491 elif isinstance(path, _unicode): 492 path = os.getcwdu() 493 else: 494 path = os.getcwd() 495 return normpath(path) 496 497# realpath is a no-op on systems without islink support 498realpath = abspath 499# Win9x family and earlier have no Unicode filename support. 500supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and 501 sys.getwindowsversion()[3] >= 2) 502 503def _abspath_split(path): 504 abs = abspath(normpath(path)) 505 prefix, rest = splitunc(abs) 506 is_unc = bool(prefix) 507 if not is_unc: 508 prefix, rest = splitdrive(abs) 509 return is_unc, prefix, [x for x in rest.split(sep) if x] 510 511def relpath(path, start=curdir): 512 """Return a relative version of a path""" 513 514 if not path: 515 raise ValueError("no path specified") 516 517 start_is_unc, start_prefix, start_list = _abspath_split(start) 518 path_is_unc, path_prefix, path_list = _abspath_split(path) 519 520 if path_is_unc ^ start_is_unc: 521 raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" 522 % (path, start)) 523 if path_prefix.lower() != start_prefix.lower(): 524 if path_is_unc: 525 raise ValueError("path is on UNC root %s, start on UNC root %s" 526 % (path_prefix, start_prefix)) 527 else: 528 raise ValueError("path is on drive %s, start on drive %s" 529 % (path_prefix, start_prefix)) 530 # Work out how much of the filepath is shared by start and path. 531 i = 0 532 for e1, e2 in zip(start_list, path_list): 533 if e1.lower() != e2.lower(): 534 break 535 i += 1 536 537 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 538 if not rel_list: 539 return curdir 540 return join(*rel_list) 541 542try: 543 # The genericpath.isdir implementation uses os.stat and checks the mode 544 # attribute to tell whether or not the path is a directory. 545 # This is overkill on Windows - just pass the path to GetFileAttributes 546 # and check the attribute from there. 547 from nt import _isdir as isdir 548except ImportError: 549 # Use genericpath.isdir as imported above. 550 pass 551