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