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