ntpath.py 16.2 KB
Newer Older
1
# Module 'ntpath' -- common operations on WinNT/Win95 pathnames
Tim Peters's avatar
Tim Peters committed
2
"""Common pathname manipulations, WindowsNT/95 version.
3 4 5

Instead of importing this module directly, import os and refer to this
module as os.path.
6
"""
7 8 9

import os
import stat
10
import sys
11

12 13
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
           "basename","dirname","commonprefix","getsize","getmtime",
14 15 16 17
           "getatime","getctime", "islink","exists","lexists","isdir","isfile",
           "ismount","walk","expanduser","expandvars","normpath","abspath",
           "splitunc","curdir","pardir","sep","pathsep","defpath","altsep",
           "extsep","devnull","realpath","supports_unicode_filenames"]
18

19 20 21 22 23 24
# strings representing various path-related bits and pieces
curdir = '.'
pardir = '..'
extsep = '.'
sep = '\\'
pathsep = ';'
25
altsep = '/'
26
defpath = '.;C:\\bin'
27 28 29
if 'ce' in sys.builtin_module_names:
    defpath = '\\Windows'
elif 'os2' in sys.builtin_module_names:
30
    # OS/2 w/ VACPP
31
    altsep = '/'
32
devnull = 'nul'
33

34 35
# Normalize the case of a pathname and map slashes to backslashes.
# Other normalizations (such as optimizing '../' away) are not done
36
# (this is done by normpath).
37

38
def normcase(s):
39 40
    """Normalize case of pathname.

41
    Makes all characters lowercase and all slashes into backslashes."""
42
    return s.replace("/", "\\").lower()
43

44

45
# Return whether a path is absolute.
46 47
# Trivial in Posix, harder on the Mac or MS-DOS.
# For DOS it is absolute if it starts with a slash or backslash (current
48 49
# volume), or if a pathname after the volume letter and colon / UNC resource
# starts with a slash or backslash.
50 51

def isabs(s):
52 53 54
    """Test whether a path is absolute"""
    s = splitdrive(s)[1]
    return s != '' and s[:1] in '/\\'
55 56


57 58
# Join two (or more) paths.

59
def join(a, *p):
60 61 62
    """Join two or more pathname components, inserting "\\" as needed"""
    path = a
    for b in p:
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
        b_wins = 0  # set to 1 iff b makes path irrelevant
        if path == "":
            b_wins = 1

        elif isabs(b):
            # This probably wipes out path so far.  However, it's more
            # complicated if path begins with a drive letter:
            #     1. join('c:', '/a') == 'c:/a'
            #     2. join('c:/', '/a') == 'c:/a'
            # But
            #     3. join('c:/a', '/b') == '/b'
            #     4. join('c:', 'd:/') = 'd:/'
            #     5. join('c:/', 'd:/') = 'd:/'
            if path[1:2] != ":" or b[1:2] == ":":
                # Path doesn't start with a drive letter, or cases 4 and 5.
                b_wins = 1

            # Else path has a drive letter, and b doesn't but is absolute.
            elif len(path) > 3 or (len(path) == 3 and
                                   path[-1] not in "/\\"):
                # case 3
                b_wins = 1

        if b_wins:
            path = b
        else:
            # Join, and ensure there's a separator.
            assert len(path) > 0
            if path[-1] in "/\\":
                if b and b[0] in "/\\":
                    path += b[1:]
                else:
                    path += b
            elif path[-1] == ":":
                path += b
            elif b:
                if b[0] in "/\\":
                    path += b
                else:
                    path += "\\" + b
103 104 105 106 107 108
            else:
                # path is not empty and does not end with a backslash,
                # but b is empty; since, e.g., split('a/') produces
                # ('a', ''), it's best if join() adds a backslash in
                # this case.
                path += '\\'
109

110
    return path
111 112 113


# Split a path in a drive specification (a drive letter followed by a
114
# colon) and the path specification.
115 116
# It is always true that drivespec + pathspec == p
def splitdrive(p):
117 118
    """Split a pathname into drive and path specifiers. Returns a 2-tuple
"(drive,path)";  either part may be empty"""
119 120
    if p[1:2] == ':':
        return p[0:2], p[2:]
121 122 123 124 125 126 127 128 129 130 131 132 133 134
    return '', p


# Parse UNC paths
def splitunc(p):
    """Split a pathname into UNC mount point and relative path specifiers.

    Return a 2-tuple (unc, rest); either part may be empty.
    If unc is not empty, it has the form '//host/mount' (or similar
    using backslashes).  unc+rest is always the input path.
    Paths containing drive letters never have an UNC part.
    """
    if p[1:2] == ':':
        return '', p # Drive letter present
135 136 137 138 139 140 141
    firstTwo = p[0:2]
    if firstTwo == '//' or firstTwo == '\\\\':
        # is a UNC path:
        # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
        # \\machine\mountpoint\directories...
        #           directory ^^^^^^^^^^^^^^^
        normp = normcase(p)
142
        index = normp.find('\\', 2)
143 144 145
        if index == -1:
            ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
            return ("", p)
146
        index = normp.find('\\', index + 1)
147 148 149
        if index == -1:
            index = len(p)
        return p[:index], p[index:]
150
    return '', p
151 152 153


# Split a path in head (everything up to the last '/') and tail (the
154
# rest).  After the trailing '/' is stripped, the invariant
155 156 157 158
# join(head, tail) == p holds.
# The resulting head won't end in '/' unless it is the root.

def split(p):
159 160 161 162
    """Split a pathname.

    Return tuple (head, tail) where tail is everything after the final slash.
    Either part may be empty."""
163

164
    d, p = splitdrive(p)
165 166 167 168 169 170 171 172 173 174
    # set i to index beyond p's last slash
    i = len(p)
    while i and p[i-1] not in '/\\':
        i = i - 1
    head, tail = p[:i], p[i:]  # now tail has no slashes
    # remove trailing slashes from head, unless it's all slashes
    head2 = head
    while head2 and head2[-1] in '/\\':
        head2 = head2[:-1]
    head = head2 or head
175
    return d + head, tail
176 177 178


# Split a path in root and extension.
179
# The extension is everything starting at the last dot in the last
180 181 182 183
# pathname component; the root is everything before that.
# It is always true that root + ext == p.

def splitext(p):
184 185 186 187
    """Split the extension from a pathname.

    Extension is everything from the last dot to the end.
    Return (root, ext), either part may be empty."""
188 189 190 191 192 193

    i = p.rfind('.')
    if i<=max(p.rfind('/'), p.rfind('\\')):
        return p, ''
    else:
        return p[:i], p[i:]
194 195 196 197 198


# Return the tail (basename) part of a path.

def basename(p):
199 200
    """Returns the final component of a pathname"""
    return split(p)[1]
201 202 203 204 205


# Return the head (dirname) part of a path.

def dirname(p):
206 207
    """Returns the directory component of a pathname"""
    return split(p)[0]
208 209 210 211 212


# Return the longest prefix of all list elements.

def commonprefix(m):
213 214
    "Given a list of pathnames, returns the longest common leading component"
    if not m: return ''
215 216 217 218 219 220 221
    s1 = min(m)
    s2 = max(m)
    n = min(len(s1), len(s2))
    for i in xrange(n):
        if s1[i] != s2[i]:
            return s1[:i]
    return s1[:n]
222 223


224 225 226
# Get size, mtime, atime of files.

def getsize(filename):
227
    """Return the size of a file, reported by os.stat()"""
228
    return os.stat(filename).st_size
229 230

def getmtime(filename):
231
    """Return the last modification time of a file, reported by os.stat()"""
232
    return os.stat(filename).st_mtime
233 234

def getatime(filename):
235
    """Return the last access time of a file, reported by os.stat()"""
236
    return os.stat(filename).st_atime
237

238 239 240
def getctime(filename):
    """Return the creation time of a file, reported by os.stat()."""
    return os.stat(filename).st_ctime
241

242 243 244 245
# Is a path a symbolic link?
# This will always return false on systems where posix.lstat doesn't exist.

def islink(path):
246
    """Test for symbolic link.  On WindowsNT/95 always returns false"""
247
    return False
248 249 250 251 252


# Does a path exist?

def exists(path):
253 254 255 256
    """Test whether a path exists"""
    try:
        st = os.stat(path)
    except os.error:
257 258
        return False
    return True
259

260 261
lexists = exists

262 263 264 265 266 267

# Is a path a dos directory?
# This follows symbolic links, so both islink() and isdir() can be true
# for the same path.

def isdir(path):
268 269 270 271
    """Test whether a path is a directory"""
    try:
        st = os.stat(path)
    except os.error:
272
        return False
273
    return stat.S_ISDIR(st.st_mode)
274 275 276 277 278 279 280


# Is a path a regular file?
# This follows symbolic links, so both islink() and isdir() can be true
# for the same path.

def isfile(path):
281 282 283 284
    """Test whether a path is a regular file"""
    try:
        st = os.stat(path)
    except os.error:
285
        return False
286
    return stat.S_ISREG(st.st_mode)
287 288


289 290
# Is a path a mount point?  Either a root (with or without drive letter)
# or an UNC path with at most a / or \ after the mount point.
291 292

def ismount(path):
293
    """Test whether a path is a mount point (defined as root of drive)"""
294 295 296
    unc, rest = splitunc(path)
    if unc:
        return rest in ("", "/", "\\")
297
    p = splitdrive(path)[1]
298
    return len(p) == 1 and p[0] in '/\\'
299 300 301 302 303 304


# Directory tree walk.
# For each directory under top (including top itself, but excluding
# '.' and '..'), func(arg, dirname, filenames) is called, where
# dirname is the name of the directory and filenames is the list
305
# of files (and subdirectories etc.) in the directory.
306 307 308 309
# The func may modify the filenames list, to implement a filter,
# or to impose a different order of visiting.

def walk(top, func, arg):
310 311 312 313 314 315 316 317 318 319 320 321 322
    """Directory tree walk with callback function.

    For each directory in the directory tree rooted at top (including top
    itself, but excluding '.' and '..'), call func(arg, dirname, fnames).
    dirname is the name of the directory, and fnames a list of the names of
    the files and subdirectories in dirname (excluding '.' and '..').  func
    may modify the fnames list in-place (e.g. via del or slice assignment),
    and walk will only recurse into the subdirectories whose names remain in
    fnames; this can be used to implement a filter, or to impose a specific
    order of visiting.  No semantics are defined for, or required of, arg,
    beyond that arg is always passed to func.  It can be used, e.g., to pass
    a filename pattern, or a mutable object designed to accumulate
    statistics.  Passing None for arg is common."""
323

324 325 326 327 328 329 330 331 332 333 334
    try:
        names = os.listdir(top)
    except os.error:
        return
    func(arg, top, names)
    exceptions = ('.', '..')
    for name in names:
        if name not in exceptions:
            name = join(top, name)
            if isdir(name):
                walk(name, func, arg)
335 336 337 338 339 340 341 342 343 344 345 346


# Expand paths beginning with '~' or '~user'.
# '~' means $HOME; '~user' means that user's home directory.
# If the path doesn't begin with '~', or if the user or $HOME is unknown,
# the path is returned unchanged (leaving error reporting to whatever
# function is called with the expanded path as argument).
# See also module 'glob' for expansion of *, ? and [...] in pathnames.
# (A function should also be defined to do full *sh-style environment
# variable expansion.)

def expanduser(path):
347 348 349
    """Expand ~ and ~user constructs.

    If user or $HOME is unknown, do nothing."""
350
    if path[:1] != '~':
351 352 353
        return path
    i, n = 1, len(path)
    while i < n and path[i] not in '/\\':
354
        i = i + 1
355
    if i == 1:
356
        if 'HOME' in os.environ:
357
            userhome = os.environ['HOME']
358
        elif not 'HOMEPATH' in os.environ:
359 360 361
            return path
        else:
            try:
362
                drive = os.environ['HOMEDRIVE']
363 364 365 366 367 368
            except KeyError:
                drive = ''
            userhome = join(drive, os.environ['HOMEPATH'])
    else:
        return path
    return userhome + path[i:]
369 370 371 372


# Expand paths containing shell variable substitutions.
# The following rules apply:
373 374 375 376
#       - no expansion within single quotes
#       - no escape character, except for '$$' which is translated into '$'
#       - ${varname} is accepted.
#       - varnames can be made out of letters, digits and the character '_'
377 378 379
# XXX With COMMAND.COM you can use any characters in a variable name,
# XXX except '^|<>='.

Tim Peters's avatar
Tim Peters committed
380
def expandvars(path):
381 382 383
    """Expand shell variables of form $var and ${var}.

    Unknown variables are left unchanged."""
384 385
    if '$' not in path:
        return path
386
    import string
387
    varchars = string.ascii_letters + string.digits + '_-'
388 389 390 391 392 393 394 395 396
    res = ''
    index = 0
    pathlen = len(path)
    while index < pathlen:
        c = path[index]
        if c == '\'':   # no expansion within single quotes
            path = path[index + 1:]
            pathlen = len(path)
            try:
397
                index = path.index('\'')
398
                res = res + '\'' + path[:index + 1]
399
            except ValueError:
400
                res = res + path
401
                index = pathlen - 1
402 403 404 405 406 407 408 409
        elif c == '$':  # variable or '$$'
            if path[index + 1:index + 2] == '$':
                res = res + c
                index = index + 1
            elif path[index + 1:index + 2] == '{':
                path = path[index+2:]
                pathlen = len(path)
                try:
410
                    index = path.index('}')
411
                    var = path[:index]
412
                    if var in os.environ:
413
                        res = res + os.environ[var]
414
                except ValueError:
415 416 417 418 419 420 421 422 423 424
                    res = res + path
                    index = pathlen - 1
            else:
                var = ''
                index = index + 1
                c = path[index:index + 1]
                while c != '' and c in varchars:
                    var = var + c
                    index = index + 1
                    c = path[index:index + 1]
425
                if var in os.environ:
426 427 428 429 430 431 432
                    res = res + os.environ[var]
                if c != '':
                    res = res + c
        else:
            res = res + c
        index = index + 1
    return res
433 434


435
# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
436 437
# Previously, this function also truncated pathnames to 8+3 format,
# but as this module is called "ntpath", that's obviously wrong!
438 439

def normpath(path):
440
    """Normalize path, eliminating double slashes, etc."""
441
    path = path.replace("/", "\\")
442
    prefix, path = splitdrive(path)
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
    # We need to be careful here. If the prefix is empty, and the path starts
    # with a backslash, it could either be an absolute path on the current
    # drive (\dir1\dir2\file) or a UNC filename (\\server\mount\dir1\file). It
    # is therefore imperative NOT to collapse multiple backslashes blindly in
    # that case.
    # The code below preserves multiple backslashes when there is no drive
    # letter. This means that the invalid filename \\\a\b is preserved
    # unchanged, where a\\\b is normalised to a\b. It's not clear that there
    # is any better behaviour for such edge cases.
    if prefix == '':
        # No drive letter - preserve initial backslashes
        while path[:1] == "\\":
            prefix = prefix + "\\"
            path = path[1:]
    else:
        # We have a drive letter - collapse initial backslashes
        if path.startswith("\\"):
            prefix = prefix + "\\"
            path = path.lstrip("\\")
462
    comps = path.split("\\")
463 464
    i = 0
    while i < len(comps):
465
        if comps[i] in ('.', ''):
466
            del comps[i]
467 468 469 470 471 472 473 474
        elif comps[i] == '..':
            if i > 0 and comps[i-1] != '..':
                del comps[i-1:i+1]
                i -= 1
            elif i == 0 and prefix.endswith("\\"):
                del comps[i]
            else:
                i += 1
475
        else:
476
            i += 1
477 478 479
    # If the path is now empty, substitute '.'
    if not prefix and not comps:
        comps.append('.')
480
    return prefix + "\\".join(comps)
Guido van Rossum's avatar
Guido van Rossum committed
481 482 483


# Return an absolute path.
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
try:
    from nt import _getfullpathname

except ImportError: # not running on Windows - mock up something sensible
    def abspath(path):
        """Return the absolute version of a path."""
        if not isabs(path):
            path = join(os.getcwd(), path)
        return normpath(path)

else:  # use native Windows method on Windows
    def abspath(path):
        """Return the absolute version of a path."""

        if path: # Empty path must return current working directory.
            try:
                path = _getfullpathname(path)
            except WindowsError:
                pass # Bad path - return unchanged.
        else:
            path = os.getcwd()
        return normpath(path)
506 507 508

# realpath is a no-op on systems without islink support
realpath = abspath
509
# Win9x family and earlier have no Unicode filename support.
510 511
supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
                              sys.getwindowsversion()[3] >= 2)