Results of a rewrite pass
[python/dscho.git] / Lib / ntpath.py
blobd6b69208df4e870947c4a5ffef1eb5429ab3ef73
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
12 __all__ = ["normcase","isabs","join","splitdrive","split","splitext",
13 "basename","dirname","commonprefix","getsize","getmtime",
14 "getatime","getctime", "islink","exists","isdir","isfile","ismount",
15 "walk","expanduser","expandvars","normpath","abspath","splitunc",
16 "supports_unicode_filenames"]
18 # Normalize the case of a pathname and map slashes to backslashes.
19 # Other normalizations (such as optimizing '../' away) are not done
20 # (this is done by normpath).
22 def normcase(s):
23 """Normalize case of pathname.
25 Makes all characters lowercase and all slashes into backslashes."""
26 return s.replace("/", "\\").lower()
29 # Return whether a path is absolute.
30 # Trivial in Posix, harder on the Mac or MS-DOS.
31 # For DOS it is absolute if it starts with a slash or backslash (current
32 # volume), or if a pathname after the volume letter and colon / UNC resource
33 # starts with a slash or backslash.
35 def isabs(s):
36 """Test whether a path is absolute"""
37 s = splitdrive(s)[1]
38 return s != '' and s[:1] in '/\\'
41 # Join two (or more) paths.
43 def join(a, *p):
44 """Join two or more pathname components, inserting "\\" as needed"""
45 path = a
46 for b in p:
47 b_wins = 0 # set to 1 iff b makes path irrelevant
48 if path == "":
49 b_wins = 1
51 elif isabs(b):
52 # This probably wipes out path so far. However, it's more
53 # complicated if path begins with a drive letter:
54 # 1. join('c:', '/a') == 'c:/a'
55 # 2. join('c:/', '/a') == 'c:/a'
56 # But
57 # 3. join('c:/a', '/b') == '/b'
58 # 4. join('c:', 'd:/') = 'd:/'
59 # 5. join('c:/', 'd:/') = 'd:/'
60 if path[1:2] != ":" or b[1:2] == ":":
61 # Path doesn't start with a drive letter, or cases 4 and 5.
62 b_wins = 1
64 # Else path has a drive letter, and b doesn't but is absolute.
65 elif len(path) > 3 or (len(path) == 3 and
66 path[-1] not in "/\\"):
67 # case 3
68 b_wins = 1
70 if b_wins:
71 path = b
72 else:
73 # Join, and ensure there's a separator.
74 assert len(path) > 0
75 if path[-1] in "/\\":
76 if b and b[0] in "/\\":
77 path += b[1:]
78 else:
79 path += b
80 elif path[-1] == ":":
81 path += b
82 elif b:
83 if b[0] in "/\\":
84 path += b
85 else:
86 path += "\\" + b
87 else:
88 # path is not empty and does not end with a backslash,
89 # but b is empty; since, e.g., split('a/') produces
90 # ('a', ''), it's best if join() adds a backslash in
91 # this case.
92 path += '\\'
94 return path
97 # Split a path in a drive specification (a drive letter followed by a
98 # colon) and the path specification.
99 # It is always true that drivespec + pathspec == p
100 def splitdrive(p):
101 """Split a pathname into drive and path specifiers. Returns a 2-tuple
102 "(drive,path)"; either part may be empty"""
103 if p[1:2] == ':':
104 return p[0:2], p[2:]
105 return '', p
108 # Parse UNC paths
109 def splitunc(p):
110 """Split a pathname into UNC mount point and relative path specifiers.
112 Return a 2-tuple (unc, rest); either part may be empty.
113 If unc is not empty, it has the form '//host/mount' (or similar
114 using backslashes). unc+rest is always the input path.
115 Paths containing drive letters never have an UNC part.
117 if p[1:2] == ':':
118 return '', p # Drive letter present
119 firstTwo = p[0:2]
120 if firstTwo == '//' or firstTwo == '\\\\':
121 # is a UNC path:
122 # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
123 # \\machine\mountpoint\directories...
124 # directory ^^^^^^^^^^^^^^^
125 normp = normcase(p)
126 index = normp.find('\\', 2)
127 if index == -1:
128 ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
129 return ("", p)
130 index = normp.find('\\', index + 1)
131 if index == -1:
132 index = len(p)
133 return p[:index], p[index:]
134 return '', p
137 # Split a path in head (everything up to the last '/') and tail (the
138 # rest). After the trailing '/' is stripped, the invariant
139 # join(head, tail) == p holds.
140 # The resulting head won't end in '/' unless it is the root.
142 def split(p):
143 """Split a pathname.
145 Return tuple (head, tail) where tail is everything after the final slash.
146 Either part may be empty."""
148 d, p = splitdrive(p)
149 # set i to index beyond p's last slash
150 i = len(p)
151 while i and p[i-1] not in '/\\':
152 i = i - 1
153 head, tail = p[:i], p[i:] # now tail has no slashes
154 # remove trailing slashes from head, unless it's all slashes
155 head2 = head
156 while head2 and head2[-1] in '/\\':
157 head2 = head2[:-1]
158 head = head2 or head
159 return d + head, tail
162 # Split a path in root and extension.
163 # The extension is everything starting at the last dot in the last
164 # pathname component; the root is everything before that.
165 # It is always true that root + ext == p.
167 def splitext(p):
168 """Split the extension from a pathname.
170 Extension is everything from the last dot to the end.
171 Return (root, ext), either part may be empty."""
173 i = p.rfind('.')
174 if i<=max(p.rfind('/'), p.rfind('\\')):
175 return p, ''
176 else:
177 return p[:i], p[i:]
180 # Return the tail (basename) part of a path.
182 def basename(p):
183 """Returns the final component of a pathname"""
184 return split(p)[1]
187 # Return the head (dirname) part of a path.
189 def dirname(p):
190 """Returns the directory component of a pathname"""
191 return split(p)[0]
194 # Return the longest prefix of all list elements.
196 def commonprefix(m):
197 "Given a list of pathnames, returns the longest common leading component"
198 if not m: return ''
199 prefix = m[0]
200 for item in m:
201 for i in range(len(prefix)):
202 if prefix[:i+1] != item[:i+1]:
203 prefix = prefix[:i]
204 if i == 0: return ''
205 break
206 return prefix
209 # Get size, mtime, atime of files.
211 def getsize(filename):
212 """Return the size of a file, reported by os.stat()"""
213 return os.stat(filename).st_size
215 def getmtime(filename):
216 """Return the last modification time of a file, reported by os.stat()"""
217 return os.stat(filename).st_mtime
219 def getatime(filename):
220 """Return the last access time of a file, reported by os.stat()"""
221 return os.stat(filename).st_atime
223 def getctime(filename):
224 """Return the creation time of a file, reported by os.stat()."""
225 return os.stat(filename).st_ctime
227 # Is a path a symbolic link?
228 # This will always return false on systems where posix.lstat doesn't exist.
230 def islink(path):
231 """Test for symbolic link. On WindowsNT/95 always returns false"""
232 return False
235 # Does a path exist?
236 # This is false for dangling symbolic links.
238 def exists(path):
239 """Test whether a path exists"""
240 try:
241 st = os.stat(path)
242 except os.error:
243 return False
244 return True
247 # Is a path a dos directory?
248 # This follows symbolic links, so both islink() and isdir() can be true
249 # for the same path.
251 def isdir(path):
252 """Test whether a path is a directory"""
253 try:
254 st = os.stat(path)
255 except os.error:
256 return False
257 return stat.S_ISDIR(st.st_mode)
260 # Is a path a regular file?
261 # This follows symbolic links, so both islink() and isdir() can be true
262 # for the same path.
264 def isfile(path):
265 """Test whether a path is a regular file"""
266 try:
267 st = os.stat(path)
268 except os.error:
269 return False
270 return stat.S_ISREG(st.st_mode)
273 # Is a path a mount point? Either a root (with or without drive letter)
274 # or an UNC path with at most a / or \ after the mount point.
276 def ismount(path):
277 """Test whether a path is a mount point (defined as root of drive)"""
278 unc, rest = splitunc(path)
279 if unc:
280 return rest in ("", "/", "\\")
281 p = splitdrive(path)[1]
282 return len(p) == 1 and p[0] in '/\\'
285 # Directory tree walk.
286 # For each directory under top (including top itself, but excluding
287 # '.' and '..'), func(arg, dirname, filenames) is called, where
288 # dirname is the name of the directory and filenames is the list
289 # files files (and subdirectories etc.) in the directory.
290 # The func may modify the filenames list, to implement a filter,
291 # or to impose a different order of visiting.
293 def walk(top, func, arg):
294 """Directory tree walk with callback function.
296 For each directory in the directory tree rooted at top (including top
297 itself, but excluding '.' and '..'), call func(arg, dirname, fnames).
298 dirname is the name of the directory, and fnames a list of the names of
299 the files and subdirectories in dirname (excluding '.' and '..'). func
300 may modify the fnames list in-place (e.g. via del or slice assignment),
301 and walk will only recurse into the subdirectories whose names remain in
302 fnames; this can be used to implement a filter, or to impose a specific
303 order of visiting. No semantics are defined for, or required of, arg,
304 beyond that arg is always passed to func. It can be used, e.g., to pass
305 a filename pattern, or a mutable object designed to accumulate
306 statistics. Passing None for arg is common."""
308 try:
309 names = os.listdir(top)
310 except os.error:
311 return
312 func(arg, top, names)
313 exceptions = ('.', '..')
314 for name in names:
315 if name not in exceptions:
316 name = join(top, name)
317 if isdir(name):
318 walk(name, func, arg)
321 # Expand paths beginning with '~' or '~user'.
322 # '~' means $HOME; '~user' means that user's home directory.
323 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
324 # the path is returned unchanged (leaving error reporting to whatever
325 # function is called with the expanded path as argument).
326 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
327 # (A function should also be defined to do full *sh-style environment
328 # variable expansion.)
330 def expanduser(path):
331 """Expand ~ and ~user constructs.
333 If user or $HOME is unknown, do nothing."""
334 if path[:1] != '~':
335 return path
336 i, n = 1, len(path)
337 while i < n and path[i] not in '/\\':
338 i = i + 1
339 if i == 1:
340 if 'HOME' in os.environ:
341 userhome = os.environ['HOME']
342 elif not 'HOMEPATH' in os.environ:
343 return path
344 else:
345 try:
346 drive = os.environ['HOMEDRIVE']
347 except KeyError:
348 drive = ''
349 userhome = join(drive, os.environ['HOMEPATH'])
350 else:
351 return path
352 return userhome + path[i:]
355 # Expand paths containing shell variable substitutions.
356 # The following rules apply:
357 # - no expansion within single quotes
358 # - no escape character, except for '$$' which is translated into '$'
359 # - ${varname} is accepted.
360 # - varnames can be made out of letters, digits and the character '_'
361 # XXX With COMMAND.COM you can use any characters in a variable name,
362 # XXX except '^|<>='.
364 def expandvars(path):
365 """Expand shell variables of form $var and ${var}.
367 Unknown variables are left unchanged."""
368 if '$' not in path:
369 return path
370 import string
371 varchars = string.ascii_letters + string.digits + '_-'
372 res = ''
373 index = 0
374 pathlen = len(path)
375 while index < pathlen:
376 c = path[index]
377 if c == '\'': # no expansion within single quotes
378 path = path[index + 1:]
379 pathlen = len(path)
380 try:
381 index = path.index('\'')
382 res = res + '\'' + path[:index + 1]
383 except ValueError:
384 res = res + path
385 index = pathlen - 1
386 elif c == '$': # variable or '$$'
387 if path[index + 1:index + 2] == '$':
388 res = res + c
389 index = index + 1
390 elif path[index + 1:index + 2] == '{':
391 path = path[index+2:]
392 pathlen = len(path)
393 try:
394 index = path.index('}')
395 var = path[:index]
396 if var in os.environ:
397 res = res + os.environ[var]
398 except ValueError:
399 res = res + path
400 index = pathlen - 1
401 else:
402 var = ''
403 index = index + 1
404 c = path[index:index + 1]
405 while c != '' and c in varchars:
406 var = var + c
407 index = index + 1
408 c = path[index:index + 1]
409 if var in os.environ:
410 res = res + os.environ[var]
411 if c != '':
412 res = res + c
413 else:
414 res = res + c
415 index = index + 1
416 return res
419 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
420 # Previously, this function also truncated pathnames to 8+3 format,
421 # but as this module is called "ntpath", that's obviously wrong!
423 def normpath(path):
424 """Normalize path, eliminating double slashes, etc."""
425 path = path.replace("/", "\\")
426 prefix, path = splitdrive(path)
427 while path[:1] == "\\":
428 prefix = prefix + "\\"
429 path = path[1:]
430 comps = path.split("\\")
431 i = 0
432 while i < len(comps):
433 if comps[i] in ('.', ''):
434 del comps[i]
435 elif comps[i] == '..':
436 if i > 0 and comps[i-1] != '..':
437 del comps[i-1:i+1]
438 i -= 1
439 elif i == 0 and prefix.endswith("\\"):
440 del comps[i]
441 else:
442 i += 1
443 else:
444 i += 1
445 # If the path is now empty, substitute '.'
446 if not prefix and not comps:
447 comps.append('.')
448 return prefix + "\\".join(comps)
451 # Return an absolute path.
452 def abspath(path):
453 """Return the absolute version of a path"""
454 try:
455 from nt import _getfullpathname
456 except ImportError: # Not running on Windows - mock up something sensible.
457 global abspath
458 def _abspath(path):
459 if not isabs(path):
460 path = join(os.getcwd(), path)
461 return normpath(path)
462 abspath = _abspath
463 return _abspath(path)
465 if path: # Empty path must return current working directory.
466 try:
467 path = _getfullpathname(path)
468 except WindowsError:
469 pass # Bad path - return unchanged.
470 else:
471 path = os.getcwd()
472 return normpath(path)
474 # realpath is a no-op on systems without islink support
475 realpath = abspath
476 # Win9x family and earlier have no Unicode filename support.
477 supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
478 sys.getwindowsversion()[3] >= 2)