Home | History | Annotate | Download | only in Lib
      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     pparts = p.split(':', 2)
    126     numparts = len(pparts)
    127     if numparts == 2:
    128         return pparts[0] + ':', pparts[1]
    129     else:
    130         if numparts == 1:
    131           return '', pparts[0]
    132     return '', p
    133 
    134 
    135 # Parse UNC paths
    136 def splitunc(p):
    137     """Split a pathname into UNC mount point and relative path specifiers.
    138 
    139     Return a 2-tuple (unc, rest); either part may be empty.
    140     If unc is not empty, it has the form '//host/mount' (or similar
    141     using backslashes).  unc+rest is always the input path.
    142     Paths containing drive letters never have an UNC part.
    143     """
    144     if len(p.split(':', 2)) > 1:
    145         return '', p # Drive letter present
    146     firstTwo = p[0:2]
    147     if firstTwo == '//' or firstTwo == '\\\\':
    148         # is a UNC path:
    149         # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
    150         # \\machine\mountpoint\directories...
    151         #           directory ^^^^^^^^^^^^^^^
    152         normp = normcase(p)
    153         index = normp.find('\\', 2)
    154         if index == -1:
    155             ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
    156             return ("", p)
    157         index = normp.find('\\', index + 1)
    158         if index == -1:
    159             index = len(p)
    160         return p[:index], p[index:]
    161     return '', p
    162 
    163 
    164 # Split a path in head (everything up to the last '/') and tail (the
    165 # rest).  After the trailing '/' is stripped, the invariant
    166 # join(head, tail) == p holds.
    167 # The resulting head won't end in '/' unless it is the root.
    168 
    169 def split(p):
    170     """Split a pathname.
    171 
    172     Return tuple (head, tail) where tail is everything after the final slash.
    173     Either part may be empty."""
    174 
    175     d, p = splitdrive(p)
    176     # set i to index beyond p's last slash

    177     i = len(p)
    178     while i and p[i-1] not in '/\\':
    179         i = i - 1
    180     head, tail = p[:i], p[i:]  # now tail has no slashes
    181     # remove trailing slashes from head, unless it's all slashes
    182     head2 = head
    183     while head2 and head2[-1] in '/\\':
    184         head2 = head2[:-1]
    185     head = head2 or head
    186     return d + head, tail
    187 
    188 
    189 # Split a path in root and extension.
    190 # The extension is everything starting at the last dot in the last
    191 # pathname component; the root is everything before that.
    192 # It is always true that root + ext == p.
    193 
    194 def splitext(p):
    195     return genericpath._splitext(p, sep, altsep, extsep)
    196 splitext.__doc__ = genericpath._splitext.__doc__
    197 
    198 
    199 # Return the tail (basename) part of a path.
    200 
    201 def basename(p):
    202     """Returns the final component of a pathname"""
    203     return split(p)[1]
    204 
    205 
    206 # Return the head (dirname) part of a path.
    207 
    208 def dirname(p):
    209     """Returns the directory component of a pathname"""
    210     return split(p)[0]
    211 
    212 # Is a path a symbolic link?
    213 # This will always return false on systems where posix.lstat doesn't exist.
    214 
    215 def islink(path):
    216     """Test for symbolic link.
    217     On WindowsNT/95 and OS/2 always returns false
    218     """
    219     return False
    220 
    221 # alias exists to lexists

    222 lexists = exists
    223 
    224 # Is a path a mount point?  Either a root (with or without drive letter)

    225 # or an UNC path with at most a / or \ after the mount point.

    226 
    227 def ismount(path):
    228     """Test whether a path is a mount point (defined as root of drive)"""
    229     unc, rest = splitunc(path)
    230     if unc:
    231         return rest in ("", "/", "\\")
    232     p = splitdrive(path)[1]
    233     return len(p) == 1 and p[0] in '/\\'
    234 
    235 
    236 # Directory tree walk.
    237 # For each directory under top (including top itself, but excluding
    238 # '.' and '..'), func(arg, dirname, filenames) is called, where
    239 # dirname is the name of the directory and filenames is the list
    240 # of files (and subdirectories etc.) in the directory.
    241 # The func may modify the filenames list, to implement a filter,
    242 # or to impose a different order of visiting.
    243 
    244 def walk(top, func, arg):
    245     """Directory tree walk with callback function.
    246 
    247     For each directory in the directory tree rooted at top (including top
    248     itself, but excluding '.' and '..'), call func(arg, dirname, fnames).
    249     dirname is the name of the directory, and fnames a list of the names of
    250     the files and subdirectories in dirname (excluding '.' and '..').  func
    251     may modify the fnames list in-place (e.g. via del or slice assignment),
    252     and walk will only recurse into the subdirectories whose names remain in
    253     fnames; this can be used to implement a filter, or to impose a specific
    254     order of visiting.  No semantics are defined for, or required of, arg,
    255     beyond that arg is always passed to func.  It can be used, e.g., to pass
    256     a filename pattern, or a mutable object designed to accumulate
    257     statistics.  Passing None for arg is common."""
    258     warnings.warnpy3k("In 3.x, os.path.walk is removed in favor of os.walk.",
    259                       stacklevel=2)
    260     try:
    261         names = os.listdir(top)
    262     except os.error:
    263         return
    264     func(arg, top, names)
    265     for name in names:
    266         name = join(top, name)
    267         if isdir(name):
    268             walk(name, func, arg)
    269 
    270 
    271 # Expand paths beginning with '~' or '~user'.
    272 # '~' means $HOME; '~user' means that user's home directory.
    273 # If the path doesn't begin with '~', or if the user or $HOME is unknown,

    274 # the path is returned unchanged (leaving error reporting to whatever

    275 # function is called with the expanded path as argument).

    276 # See also module 'glob' for expansion of *, ? and [...] in pathnames.

    277 # (A function should also be defined to do full *sh-style environment

    278 # variable expansion.)

    279 
    280 def expanduser(path):
    281     """Expand ~ and ~user constructs.
    282 
    283     If user or $HOME is unknown, do nothing."""
    284     if path[:1] != '~':
    285         return path
    286     i, n = 1, len(path)
    287     while i < n and path[i] not in '/\\':
    288         i = i + 1
    289 
    290     if 'HOME' in os.environ:
    291         userhome = os.environ['HOME']
    292     elif 'USERPROFILE' in os.environ:
    293         userhome = os.environ['USERPROFILE']
    294     elif not 'HOMEPATH' in os.environ:
    295         return path
    296     else:
    297         try:
    298             drive = os.environ['HOMEDRIVE']
    299         except KeyError:
    300             drive = ''
    301         userhome = join(drive, os.environ['HOMEPATH'])
    302 
    303     if i != 1: #~user
    304         userhome = join(dirname(userhome), path[1:i])
    305 
    306     return userhome + path[i:]
    307 
    308 
    309 # Expand paths containing shell variable substitutions.
    310 # The following rules apply:
    311 #       - no expansion within single quotes
    312 #       - '$$' is translated into '$'
    313 #       - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
    314 #       - ${varname} is accepted.
    315 #       - $varname is accepted.
    316 #       - %varname% is accepted.
    317 #       - varnames can be made out of letters, digits and the characters '_-'
    318 #         (though is not verified in the ${varname} and %varname% cases)
    319 # XXX With COMMAND.COM you can use any characters in a variable name,
    320 # XXX except '^|<>='.
    321 
    322 def expandvars(path):
    323     """Expand shell variables of the forms $var, ${var} and %var%.
    324 
    325     Unknown variables are left unchanged."""
    326     if '$' not in path and '%' not in path:
    327         return path
    328     import string
    329     varchars = string.ascii_letters + string.digits + '_-'
    330     res = ''
    331     index = 0
    332     pathlen = len(path)
    333     while index < pathlen:
    334         c = path[index]
    335         if c == '\'':   # no expansion within single quotes

    336             path = path[index + 1:]
    337             pathlen = len(path)
    338             try:
    339                 index = path.index('\'')
    340                 res = res + '\'' + path[:index + 1]
    341             except ValueError:
    342                 res = res + path
    343                 index = pathlen - 1
    344         elif c == '%':  # variable or '%'

    345             if path[index + 1:index + 2] == '%':
    346                 res = res + c
    347                 index = index + 1
    348             else:
    349                 path = path[index+1:]
    350                 pathlen = len(path)
    351                 try:
    352                     index = path.index('%')
    353                 except ValueError:
    354                     res = res + '%' + path
    355                     index = pathlen - 1
    356                 else:
    357                     var = path[:index]
    358                     if var in os.environ:
    359                         res = res + os.environ[var]
    360                     else:
    361                         res = res + '%' + var + '%'
    362         elif c == '$':  # variable or '$$'

    363             if path[index + 1:index + 2] == '$':
    364                 res = res + c
    365                 index = index + 1
    366             elif path[index + 1:index + 2] == '{':
    367                 path = path[index+2:]
    368                 pathlen = len(path)
    369                 try:
    370                     index = path.index('}')
    371                     var = path[:index]
    372                     if var in os.environ:
    373                         res = res + os.environ[var]
    374                     else:
    375                         res = res + '${' + var + '}'
    376                 except ValueError:
    377                     res = res + '${' + path
    378                     index = pathlen - 1
    379             else:
    380                 var = ''
    381                 index = index + 1
    382                 c = path[index:index + 1]
    383                 while c != '' and c in varchars:
    384                     var = var + c
    385                     index = index + 1
    386                     c = path[index:index + 1]
    387                 if var in os.environ:
    388                     res = res + os.environ[var]
    389                 else:
    390                     res = res + '$' + var
    391                 if c != '':
    392                     index = index - 1
    393         else:
    394             res = res + c
    395         index = index + 1
    396     return res
    397 
    398 
    399 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.

    400 # Previously, this function also truncated pathnames to 8+3 format,

    401 # but as this module is called "ntpath", that's obviously wrong!

    402 
    403 def normpath(path):
    404     """Normalize path, eliminating double slashes, etc."""
    405     # Preserve unicode (if path is unicode)

    406     backslash, dot = (u'\\', u'.') if isinstance(path, unicode) else ('\\', '.')
    407     if path.startswith(('\\\\.\\', '\\\\?\\')):
    408         # in the case of paths with these prefixes:
    409         # \\.\ -> device names
    410         # \\?\ -> literal paths
    411         # do not do any normalization, but return the path unchanged
    412         return path
    413     path = path.replace("/", "\\")
    414     prefix, path = splitdrive(path)
    415     # We need to be careful here. If the prefix is empty, and the path starts
    416     # with a backslash, it could either be an absolute path on the current
    417     # drive (\dir1\dir2\file) or a UNC filename (\\server\mount\dir1\file). It
    418     # is therefore imperative NOT to collapse multiple backslashes blindly in
    419     # that case.
    420     # The code below preserves multiple backslashes when there is no drive
    421     # letter. This means that the invalid filename \\\a\b is preserved
    422     # unchanged, where a\\\b is normalised to a\b. It's not clear that there
    423     # is any better behaviour for such edge cases.

    424     if prefix == '':
    425         # No drive letter - preserve initial backslashes

    426         while path[:1] == "\\":
    427             prefix = prefix + backslash
    428             path = path[1:]
    429     else:
    430         # We have a drive letter - collapse initial backslashes

    431         if path.startswith("\\"):
    432             prefix = prefix + backslash
    433             path = path.lstrip("\\")
    434     comps = path.split("\\")
    435     i = 0
    436     while i < len(comps):
    437         if comps[i] in ('.', ''):
    438             del comps[i]
    439         elif comps[i] == '..':
    440             if i > 0 and comps[i-1] != '..':
    441                 del comps[i-1:i+1]
    442                 i -= 1
    443             elif i == 0 and prefix.endswith("\\"):
    444                 del comps[i]
    445             else:
    446                 i += 1
    447         else:
    448             i += 1
    449     # If the path is now empty, substitute '.'

    450     if not prefix and not comps:
    451         comps.append(dot)
    452     return prefix + backslash.join(comps)
    453 
    454 
    455 # Return an absolute path.

    456 try:
    457     from nt import _getfullpathname
    458 
    459 except ImportError: # not running on Windows - mock up something sensible

    460     def abspath(path):
    461         """Return the absolute version of a path."""
    462         if not isabs(path):
    463             if isinstance(path, unicode):
    464                 cwd = os.getcwdu()
    465             else:
    466                 cwd = os.getcwd()
    467             path = join(cwd, path)
    468         return normpath(path)
    469 
    470 else:  # use native Windows method on Windows

    471     def abspath(path):
    472         """Return the absolute version of a path."""
    473 
    474         if path: # Empty path must return current working directory.

    475             try:
    476                 path = _getfullpathname(path)
    477             except WindowsError:
    478                 pass # Bad path - return unchanged.

    479         elif isinstance(path, unicode):
    480             path = os.getcwdu()
    481         else:
    482             path = os.getcwd()
    483         return normpath(path)
    484 
    485 # realpath is a no-op on systems without islink support

    486 realpath = abspath
    487 # Win9x family and earlier have no Unicode filename support.

    488 supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
    489                               sys.getwindowsversion()[3] >= 2)
    490 
    491 def _abspath_split(path):
    492     abs = abspath(normpath(path))
    493     prefix, rest = splitunc(abs)
    494     is_unc = bool(prefix)
    495     if not is_unc:
    496         prefix, rest = splitdrive(abs)
    497     return is_unc, prefix, [x for x in rest.split(sep) if x]
    498 
    499 def relpath(path, start=curdir):
    500     """Return a relative version of a path"""
    501 
    502     if not path:
    503         raise ValueError("no path specified")
    504 
    505     start_is_unc, start_prefix, start_list = _abspath_split(start)
    506     path_is_unc, path_prefix, path_list = _abspath_split(path)
    507 
    508     if path_is_unc ^ start_is_unc:
    509         raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)"
    510                                                             % (path, start))
    511     if path_prefix.lower() != start_prefix.lower():
    512         if path_is_unc:
    513             raise ValueError("path is on UNC root %s, start on UNC root %s"
    514                                                 % (path_prefix, start_prefix))
    515         else:
    516             raise ValueError("path is on drive %s, start on drive %s"
    517                                                 % (path_prefix, start_prefix))
    518     # Work out how much of the filepath is shared by start and path.

    519     i = 0
    520     for e1, e2 in zip(start_list, path_list):
    521         if e1.lower() != e2.lower():
    522             break
    523         i += 1
    524 
    525     rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
    526     if not rel_list:
    527         return curdir
    528     return join(*rel_list)
    529