ntpath.py 12.6 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

11 12 13 14
__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
           "basename","dirname","commonprefix","getsize","getmtime",
           "getatime","islink","exists","isdir","isfile","ismount",
           "walk","expanduser","expandvars","normpath","abspath","splitunc"]
15

16 17
# Normalize the case of a pathname and map slashes to backslashes.
# Other normalizations (such as optimizing '../' away) are not done
18
# (this is done by normpath).
19

20
def normcase(s):
21 22
    """Normalize case of pathname.

23
    Makes all characters lowercase and all slashes into backslashes."""
24
    return s.replace("/", "\\").lower()
25

26

27
# Return whether a path is absolute.
28 29
# Trivial in Posix, harder on the Mac or MS-DOS.
# For DOS it is absolute if it starts with a slash or backslash (current
30 31
# volume), or if a pathname after the volume letter and colon / UNC resource
# starts with a slash or backslash.
32 33

def isabs(s):
34 35 36
    """Test whether a path is absolute"""
    s = splitdrive(s)[1]
    return s != '' and s[:1] in '/\\'
37 38


39 40
# Join two (or more) paths.

41
def join(a, *p):
42 43 44 45 46
    """Join two or more pathname components, inserting "\\" as needed"""
    path = a
    for b in p:
        if isabs(b):
            path = b
47
        elif path == '' or path[-1:] in '/\\:':
48 49
            path = path + b
        else:
50
            path = path + "\\" + b
51
    return path
52 53 54


# Split a path in a drive specification (a drive letter followed by a
55
# colon) and the path specification.
56 57
# It is always true that drivespec + pathspec == p
def splitdrive(p):
58 59
    """Split a pathname into drive and path specifiers. Returns a 2-tuple
"(drive,path)";  either part may be empty"""
60 61
    if p[1:2] == ':':
        return p[0:2], p[2:]
62 63 64 65 66 67 68 69 70 71 72 73 74 75
    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
76 77 78 79 80 81 82
    firstTwo = p[0:2]
    if firstTwo == '//' or firstTwo == '\\\\':
        # is a UNC path:
        # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
        # \\machine\mountpoint\directories...
        #           directory ^^^^^^^^^^^^^^^
        normp = normcase(p)
83
        index = normp.find('\\', 2)
84 85 86
        if index == -1:
            ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
            return ("", p)
87
        index = normp.find('\\', index + 1)
88 89 90
        if index == -1:
            index = len(p)
        return p[:index], p[index:]
91
    return '', p
92 93 94


# Split a path in head (everything up to the last '/') and tail (the
95
# rest).  After the trailing '/' is stripped, the invariant
96 97 98 99
# join(head, tail) == p holds.
# The resulting head won't end in '/' unless it is the root.

def split(p):
100 101 102 103
    """Split a pathname.

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

105
    d, p = splitdrive(p)
106 107 108 109 110 111 112 113 114 115
    # 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
116
    return d + head, tail
117 118 119


# Split a path in root and extension.
120
# The extension is everything starting at the last dot in the last
121 122 123 124
# pathname component; the root is everything before that.
# It is always true that root + ext == p.

def splitext(p):
125 126 127 128
    """Split the extension from a pathname.

    Extension is everything from the last dot to the end.
    Return (root, ext), either part may be empty."""
129 130 131 132 133 134 135 136 137 138 139 140 141 142
    root, ext = '', ''
    for c in p:
        if c in ['/','\\']:
            root, ext = root + ext + c, ''
        elif c == '.':
            if ext:
                root, ext = root + ext, c
            else:
                ext = c
        elif ext:
            ext = ext + c
        else:
            root = root + c
    return root, ext
143 144 145 146 147


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

def basename(p):
148 149
    """Returns the final component of a pathname"""
    return split(p)[1]
150 151 152 153 154


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

def dirname(p):
155 156
    """Returns the directory component of a pathname"""
    return split(p)[0]
157 158 159 160 161


# Return the longest prefix of all list elements.

def commonprefix(m):
162 163
    "Given a list of pathnames, returns the longest common leading component"
    if not m: return ''
164 165
    prefix = m[0]
    for item in m:
166
        for i in range(len(prefix)):
167
            if prefix[:i+1] != item[:i+1]:
168 169 170
                prefix = prefix[:i]
                if i == 0: return ''
                break
171
    return prefix
172 173


174 175 176
# Get size, mtime, atime of files.

def getsize(filename):
177
    """Return the size of a file, reported by os.stat()"""
178 179 180 181
    st = os.stat(filename)
    return st[stat.ST_SIZE]

def getmtime(filename):
182
    """Return the last modification time of a file, reported by os.stat()"""
183 184 185 186
    st = os.stat(filename)
    return st[stat.ST_MTIME]

def getatime(filename):
187
    """Return the last access time of a file, reported by os.stat()"""
188
    st = os.stat(filename)
189
    return st[stat.ST_ATIME]
190 191


192 193 194 195
# Is a path a symbolic link?
# This will always return false on systems where posix.lstat doesn't exist.

def islink(path):
196 197
    """Test for symbolic link.  On WindowsNT/95 always returns false"""
    return 0
198 199 200 201 202 203


# Does a path exist?
# This is false for dangling symbolic links.

def exists(path):
204 205 206 207 208 209
    """Test whether a path exists"""
    try:
        st = os.stat(path)
    except os.error:
        return 0
    return 1
210 211 212 213 214 215 216


# 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):
217 218 219 220 221 222
    """Test whether a path is a directory"""
    try:
        st = os.stat(path)
    except os.error:
        return 0
    return stat.S_ISDIR(st[stat.ST_MODE])
223 224 225 226 227 228 229


# 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):
230 231 232 233 234 235
    """Test whether a path is a regular file"""
    try:
        st = os.stat(path)
    except os.error:
        return 0
    return stat.S_ISREG(st[stat.ST_MODE])
236 237


238 239
# 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.
240 241

def ismount(path):
242
    """Test whether a path is a mount point (defined as root of drive)"""
243 244 245
    unc, rest = splitunc(path)
    if unc:
        return rest in ("", "/", "\\")
246
    p = splitdrive(path)[1]
247
    return len(p) == 1 and p[0] in '/\\'
248 249 250 251 252 253 254 255 256 257 258


# 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
# files files (and subdirectories etc.) in the directory.
# The func may modify the filenames list, to implement a filter,
# or to impose a different order of visiting.

def walk(top, func, arg):
259 260
    """Directory tree walk whth callback function.

Tim Peters's avatar
Tim Peters committed
261
    walk(top, func, arg) calls func(arg, d, files) for each directory d
262 263
    in the tree rooted at top (including top itself); files is a list
    of all the files and subdirs in directory d."""
264 265 266 267 268 269 270 271 272 273 274
    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)
275 276 277 278 279 280 281 282 283 284 285 286


# 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):
287 288 289
    """Expand ~ and ~user constructs.

    If user or $HOME is unknown, do nothing."""
290
    if path[:1] != '~':
291 292 293
        return path
    i, n = 1, len(path)
    while i < n and path[i] not in '/\\':
294
        i = i + 1
295 296 297 298 299 300 301
    if i == 1:
        if os.environ.has_key('HOME'):
            userhome = os.environ['HOME']
        elif not os.environ.has_key('HOMEPATH'):
            return path
        else:
            try:
302
                drive = os.environ['HOMEDRIVE']
303 304 305 306 307 308
            except KeyError:
                drive = ''
            userhome = join(drive, os.environ['HOMEPATH'])
    else:
        return path
    return userhome + path[i:]
309 310 311 312


# Expand paths containing shell variable substitutions.
# The following rules apply:
313 314 315 316
#       - 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 '_'
317 318 319
# XXX With COMMAND.COM you can use any characters in a variable name,
# XXX except '^|<>='.

Tim Peters's avatar
Tim Peters committed
320
def expandvars(path):
321 322 323
    """Expand shell variables of form $var and ${var}.

    Unknown variables are left unchanged."""
324 325
    if '$' not in path:
        return path
326 327
    import string
    varchars = string.letters + string.digits + '_-'
328 329 330 331 332 333 334 335 336
    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:
337
                index = path.index('\'')
338
                res = res + '\'' + path[:index + 1]
339
            except ValueError:
340
                res = res + path
341
                index = pathlen - 1
342 343 344 345 346 347 348 349
        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:
350
                    index = path.index('}')
351 352 353
                    var = path[:index]
                    if os.environ.has_key(var):
                        res = res + os.environ[var]
354
                except ValueError:
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
                    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]
                if os.environ.has_key(var):
                    res = res + os.environ[var]
                if c != '':
                    res = res + c
        else:
            res = res + c
        index = index + 1
    return res
373 374 375


# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
376 377
# Previously, this function also truncated pathnames to 8+3 format,
# but as this module is called "ntpath", that's obviously wrong!
378 379

def normpath(path):
380
    """Normalize path, eliminating double slashes, etc."""
381
    path = path.replace("/", "\\")
382
    prefix, path = splitdrive(path)
383 384
    while path[:1] == "\\":
        prefix = prefix + "\\"
385
        path = path[1:]
386
    comps = path.split("\\")
387 388 389 390 391 392
    i = 0
    while i < len(comps):
        if comps[i] == '.':
            del comps[i]
        elif comps[i] == '..' and i > 0 and comps[i-1] not in ('', '..'):
            del comps[i-1:i+1]
393
            i = i - 1
394
        elif comps[i] == '' and i > 0 and comps[i-1] != '':
395 396
            del comps[i]
        else:
397
            i = i + 1
398 399 400
    # If the path is now empty, substitute '.'
    if not prefix and not comps:
        comps.append('.')
401
    return prefix + "\\".join(comps)
Guido van Rossum's avatar
Guido van Rossum committed
402 403 404 405


# Return an absolute path.
def abspath(path):
406
    """Return the absolute version of a path"""
407 408 409
    try:
        import win32api
    except ImportError:
410 411 412 413 414 415 416
        global abspath
        def _abspath(path):
            if not isabs(path):
                path = join(os.getcwd(), path)
            return normpath(path)
        abspath = _abspath
        return _abspath(path)
417 418 419 420 421 422 423
    if path: # Empty path must return current working directory.
        try:
            path = win32api.GetFullPathName(path)
        except win32api.error:
            pass # Bad path - return unchanged.
    else:
        path = os.getcwd()
424
    return normpath(path)