Change to flush and close logic to fix #1760556.
[python.git] / Lib / ntpath.py
blob06b2293293ee1cb179cb04aa50a8b4dcb2392d64
1 # Module 'ntpath' -- common operations on WinNT/Win95 pathnames
2 """Common pathname manipulations, WindowsNT/95 version.
4 Instead of importing this module directly, import os and refer to this
5 module as os.path.
6 """
8 import os
9 import stat
10 import sys
11 import genericpath
12 from genericpath import *
14 __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
15 "basename","dirname","commonprefix","getsize","getmtime",
16 "getatime","getctime", "islink","exists","lexists","isdir","isfile",
17 "ismount","walk","expanduser","expandvars","normpath","abspath",
18 "splitunc","curdir","pardir","sep","pathsep","defpath","altsep",
19 "extsep","devnull","realpath","supports_unicode_filenames","relpath"]
21 # strings representing various path-related bits and pieces
22 curdir = '.'
23 pardir = '..'
24 extsep = '.'
25 sep = '\\'
26 pathsep = ';'
27 altsep = '/'
28 defpath = '.;C:\\bin'
29 if 'ce' in sys.builtin_module_names:
30 defpath = '\\Windows'
31 elif 'os2' in sys.builtin_module_names:
32 # OS/2 w/ VACPP
33 altsep = '/'
34 devnull = 'nul'
36 # Normalize the case of a pathname and map slashes to backslashes.
37 # Other normalizations (such as optimizing '../' away) are not done
38 # (this is done by normpath).
40 def normcase(s):
41 """Normalize case of pathname.
43 Makes all characters lowercase and all slashes into backslashes."""
44 return s.replace("/", "\\").lower()
47 # Return whether a path is absolute.
48 # Trivial in Posix, harder on the Mac or MS-DOS.
49 # For DOS it is absolute if it starts with a slash or backslash (current
50 # volume), or if a pathname after the volume letter and colon / UNC resource
51 # starts with a slash or backslash.
53 def isabs(s):
54 """Test whether a path is absolute"""
55 s = splitdrive(s)[1]
56 return s != '' and s[:1] in '/\\'
59 # Join two (or more) paths.
61 def join(a, *p):
62 """Join two or more pathname components, inserting "\\" as needed.
63 If any component is an absolute path, all previous path components
64 will be discarded."""
65 path = a
66 for b in p:
67 b_wins = 0 # set to 1 iff b makes path irrelevant
68 if path == "":
69 b_wins = 1
71 elif isabs(b):
72 # This probably wipes out path so far. However, it's more
73 # complicated if path begins with a drive letter:
74 # 1. join('c:', '/a') == 'c:/a'
75 # 2. join('c:/', '/a') == 'c:/a'
76 # But
77 # 3. join('c:/a', '/b') == '/b'
78 # 4. join('c:', 'd:/') = 'd:/'
79 # 5. join('c:/', 'd:/') = 'd:/'
80 if path[1:2] != ":" or b[1:2] == ":":
81 # Path doesn't start with a drive letter, or cases 4 and 5.
82 b_wins = 1
84 # Else path has a drive letter, and b doesn't but is absolute.
85 elif len(path) > 3 or (len(path) == 3 and
86 path[-1] not in "/\\"):
87 # case 3
88 b_wins = 1
90 if b_wins:
91 path = b
92 else:
93 # Join, and ensure there's a separator.
94 assert len(path) > 0
95 if path[-1] in "/\\":
96 if b and b[0] in "/\\":
97 path += b[1:]
98 else:
99 path += b
100 elif path[-1] == ":":
101 path += b
102 elif b:
103 if b[0] in "/\\":
104 path += b
105 else:
106 path += "\\" + b
107 else:
108 # path is not empty and does not end with a backslash,
109 # but b is empty; since, e.g., split('a/') produces
110 # ('a', ''), it's best if join() adds a backslash in
111 # this case.
112 path += '\\'
114 return path
117 # Split a path in a drive specification (a drive letter followed by a
118 # colon) and the path specification.
119 # It is always true that drivespec + pathspec == p
120 def splitdrive(p):
121 """Split a pathname into drive and path specifiers. Returns a 2-tuple
122 "(drive,path)"; either part may be empty"""
123 if p[1:2] == ':':
124 return p[0:2], p[2:]
125 return '', p
128 # Parse UNC paths
129 def splitunc(p):
130 """Split a pathname into UNC mount point and relative path specifiers.
132 Return a 2-tuple (unc, rest); either part may be empty.
133 If unc is not empty, it has the form '//host/mount' (or similar
134 using backslashes). unc+rest is always the input path.
135 Paths containing drive letters never have an UNC part.
137 if p[1:2] == ':':
138 return '', p # Drive letter present
139 firstTwo = p[0:2]
140 if firstTwo == '//' or firstTwo == '\\\\':
141 # is a UNC path:
142 # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
143 # \\machine\mountpoint\directories...
144 # directory ^^^^^^^^^^^^^^^
145 normp = normcase(p)
146 index = normp.find('\\', 2)
147 if index == -1:
148 ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
149 return ("", p)
150 index = normp.find('\\', index + 1)
151 if index == -1:
152 index = len(p)
153 return p[:index], p[index:]
154 return '', p
157 # Split a path in head (everything up to the last '/') and tail (the
158 # rest). After the trailing '/' is stripped, the invariant
159 # join(head, tail) == p holds.
160 # The resulting head won't end in '/' unless it is the root.
162 def split(p):
163 """Split a pathname.
165 Return tuple (head, tail) where tail is everything after the final slash.
166 Either part may be empty."""
168 d, p = splitdrive(p)
169 # set i to index beyond p's last slash
170 i = len(p)
171 while i and p[i-1] not in '/\\':
172 i = i - 1
173 head, tail = p[:i], p[i:] # now tail has no slashes
174 # remove trailing slashes from head, unless it's all slashes
175 head2 = head
176 while head2 and head2[-1] in '/\\':
177 head2 = head2[:-1]
178 head = head2 or head
179 return d + head, tail
182 # Split a path in root and extension.
183 # The extension is everything starting at the last dot in the last
184 # pathname component; the root is everything before that.
185 # It is always true that root + ext == p.
187 def splitext(p):
188 return genericpath._splitext(p, sep, altsep, extsep)
189 splitext.__doc__ = genericpath._splitext.__doc__
192 # Return the tail (basename) part of a path.
194 def basename(p):
195 """Returns the final component of a pathname"""
196 return split(p)[1]
199 # Return the head (dirname) part of a path.
201 def dirname(p):
202 """Returns the directory component of a pathname"""
203 return split(p)[0]
205 # Is a path a symbolic link?
206 # This will always return false on systems where posix.lstat doesn't exist.
208 def islink(path):
209 """Test for symbolic link.
210 On WindowsNT/95 and OS/2 always returns false
212 return False
214 # alias exists to lexists
215 lexists = exists
217 # Is a path a mount point? Either a root (with or without drive letter)
218 # or an UNC path with at most a / or \ after the mount point.
220 def ismount(path):
221 """Test whether a path is a mount point (defined as root of drive)"""
222 unc, rest = splitunc(path)
223 if unc:
224 return rest in ("", "/", "\\")
225 p = splitdrive(path)[1]
226 return len(p) == 1 and p[0] in '/\\'
229 # Directory tree walk.
230 # For each directory under top (including top itself, but excluding
231 # '.' and '..'), func(arg, dirname, filenames) is called, where
232 # dirname is the name of the directory and filenames is the list
233 # of files (and subdirectories etc.) in the directory.
234 # The func may modify the filenames list, to implement a filter,
235 # or to impose a different order of visiting.
237 def walk(top, func, arg):
238 """Directory tree walk with callback function.
240 For each directory in the directory tree rooted at top (including top
241 itself, but excluding '.' and '..'), call func(arg, dirname, fnames).
242 dirname is the name of the directory, and fnames a list of the names of
243 the files and subdirectories in dirname (excluding '.' and '..'). func
244 may modify the fnames list in-place (e.g. via del or slice assignment),
245 and walk will only recurse into the subdirectories whose names remain in
246 fnames; this can be used to implement a filter, or to impose a specific
247 order of visiting. No semantics are defined for, or required of, arg,
248 beyond that arg is always passed to func. It can be used, e.g., to pass
249 a filename pattern, or a mutable object designed to accumulate
250 statistics. Passing None for arg is common."""
252 try:
253 names = os.listdir(top)
254 except os.error:
255 return
256 func(arg, top, names)
257 exceptions = ('.', '..')
258 for name in names:
259 if name not in exceptions:
260 name = join(top, name)
261 if isdir(name):
262 walk(name, func, arg)
265 # Expand paths beginning with '~' or '~user'.
266 # '~' means $HOME; '~user' means that user's home directory.
267 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
268 # the path is returned unchanged (leaving error reporting to whatever
269 # function is called with the expanded path as argument).
270 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
271 # (A function should also be defined to do full *sh-style environment
272 # variable expansion.)
274 def expanduser(path):
275 """Expand ~ and ~user constructs.
277 If user or $HOME is unknown, do nothing."""
278 if path[:1] != '~':
279 return path
280 i, n = 1, len(path)
281 while i < n and path[i] not in '/\\':
282 i = i + 1
284 if 'HOME' in os.environ:
285 userhome = os.environ['HOME']
286 elif 'USERPROFILE' in os.environ:
287 userhome = os.environ['USERPROFILE']
288 elif not 'HOMEPATH' in os.environ:
289 return path
290 else:
291 try:
292 drive = os.environ['HOMEDRIVE']
293 except KeyError:
294 drive = ''
295 userhome = join(drive, os.environ['HOMEPATH'])
297 if i != 1: #~user
298 userhome = join(dirname(userhome), path[1:i])
300 return userhome + path[i:]
303 # Expand paths containing shell variable substitutions.
304 # The following rules apply:
305 # - no expansion within single quotes
306 # - '$$' is translated into '$'
307 # - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
308 # - ${varname} is accepted.
309 # - $varname is accepted.
310 # - %varname% is accepted.
311 # - varnames can be made out of letters, digits and the characters '_-'
312 # (though is not verifed in the ${varname} and %varname% cases)
313 # XXX With COMMAND.COM you can use any characters in a variable name,
314 # XXX except '^|<>='.
316 def expandvars(path):
317 """Expand shell variables of the forms $var, ${var} and %var%.
319 Unknown variables are left unchanged."""
320 if '$' not in path and '%' not in path:
321 return path
322 import string
323 varchars = string.ascii_letters + string.digits + '_-'
324 res = ''
325 index = 0
326 pathlen = len(path)
327 while index < pathlen:
328 c = path[index]
329 if c == '\'': # no expansion within single quotes
330 path = path[index + 1:]
331 pathlen = len(path)
332 try:
333 index = path.index('\'')
334 res = res + '\'' + path[:index + 1]
335 except ValueError:
336 res = res + path
337 index = pathlen - 1
338 elif c == '%': # variable or '%'
339 if path[index + 1:index + 2] == '%':
340 res = res + c
341 index = index + 1
342 else:
343 path = path[index+1:]
344 pathlen = len(path)
345 try:
346 index = path.index('%')
347 except ValueError:
348 res = res + '%' + path
349 index = pathlen - 1
350 else:
351 var = path[:index]
352 if var in os.environ:
353 res = res + os.environ[var]
354 else:
355 res = res + '%' + var + '%'
356 elif c == '$': # variable or '$$'
357 if path[index + 1:index + 2] == '$':
358 res = res + c
359 index = index + 1
360 elif path[index + 1:index + 2] == '{':
361 path = path[index+2:]
362 pathlen = len(path)
363 try:
364 index = path.index('}')
365 var = path[:index]
366 if var in os.environ:
367 res = res + os.environ[var]
368 else:
369 res = res + '${' + var + '}'
370 except ValueError:
371 res = res + '${' + path
372 index = pathlen - 1
373 else:
374 var = ''
375 index = index + 1
376 c = path[index:index + 1]
377 while c != '' and c in varchars:
378 var = var + c
379 index = index + 1
380 c = path[index:index + 1]
381 if var in os.environ:
382 res = res + os.environ[var]
383 else:
384 res = res + '$' + var
385 if c != '':
386 index = index - 1
387 else:
388 res = res + c
389 index = index + 1
390 return res
393 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
394 # Previously, this function also truncated pathnames to 8+3 format,
395 # but as this module is called "ntpath", that's obviously wrong!
397 def normpath(path):
398 """Normalize path, eliminating double slashes, etc."""
399 path = path.replace("/", "\\")
400 prefix, path = splitdrive(path)
401 # We need to be careful here. If the prefix is empty, and the path starts
402 # with a backslash, it could either be an absolute path on the current
403 # drive (\dir1\dir2\file) or a UNC filename (\\server\mount\dir1\file). It
404 # is therefore imperative NOT to collapse multiple backslashes blindly in
405 # that case.
406 # The code below preserves multiple backslashes when there is no drive
407 # letter. This means that the invalid filename \\\a\b is preserved
408 # unchanged, where a\\\b is normalised to a\b. It's not clear that there
409 # is any better behaviour for such edge cases.
410 if prefix == '':
411 # No drive letter - preserve initial backslashes
412 while path[:1] == "\\":
413 prefix = prefix + "\\"
414 path = path[1:]
415 else:
416 # We have a drive letter - collapse initial backslashes
417 if path.startswith("\\"):
418 prefix = prefix + "\\"
419 path = path.lstrip("\\")
420 comps = path.split("\\")
421 i = 0
422 while i < len(comps):
423 if comps[i] in ('.', ''):
424 del comps[i]
425 elif comps[i] == '..':
426 if i > 0 and comps[i-1] != '..':
427 del comps[i-1:i+1]
428 i -= 1
429 elif i == 0 and prefix.endswith("\\"):
430 del comps[i]
431 else:
432 i += 1
433 else:
434 i += 1
435 # If the path is now empty, substitute '.'
436 if not prefix and not comps:
437 comps.append('.')
438 return prefix + "\\".join(comps)
441 # Return an absolute path.
442 try:
443 from nt import _getfullpathname
445 except ImportError: # not running on Windows - mock up something sensible
446 def abspath(path):
447 """Return the absolute version of a path."""
448 if not isabs(path):
449 path = join(os.getcwd(), path)
450 return normpath(path)
452 else: # use native Windows method on Windows
453 def abspath(path):
454 """Return the absolute version of a path."""
456 if path: # Empty path must return current working directory.
457 try:
458 path = _getfullpathname(path)
459 except WindowsError:
460 pass # Bad path - return unchanged.
461 else:
462 path = os.getcwd()
463 return normpath(path)
465 # realpath is a no-op on systems without islink support
466 realpath = abspath
467 # Win9x family and earlier have no Unicode filename support.
468 supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
469 sys.getwindowsversion()[3] >= 2)
471 def relpath(path, start=curdir):
472 """Return a relative version of a path"""
474 if not path:
475 raise ValueError("no path specified")
476 start_list = abspath(start).split(sep)
477 path_list = abspath(path).split(sep)
478 if start_list[0].lower() != path_list[0].lower():
479 unc_path, rest = splitunc(path)
480 unc_start, rest = splitunc(start)
481 if bool(unc_path) ^ bool(unc_start):
482 raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
483 % (path, start))
484 else:
485 raise ValueError("path is on drive %s, start on drive %s"
486 % (path_list[0], start_list[0]))
487 # Work out how much of the filepath is shared by start and path.
488 for i in range(min(len(start_list), len(path_list))):
489 if start_list[i].lower() != path_list[i].lower():
490 break
491 else:
492 i += 1
494 rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
495 return join(*rel_list)