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 *
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
24curdir = '.'
25pardir = '..'
26extsep = '.'
27sep = '\\'
28pathsep = ';'
29altsep = '/'
30defpath = '.;C:\\bin'
31if 'ce' in sys.builtin_module_names:
32    defpath = '\\Windows'
33elif 'os2' in sys.builtin_module_names:
34    # OS/2 w/ VACPP
35    altsep = '/'
36devnull = '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
42def 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
55def 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
63def 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
122def 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
131def 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
164def 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
189def splitext(p):
190    return genericpath._splitext(p, sep, altsep, extsep)
191splitext.__doc__ = genericpath._splitext.__doc__
192
193
194# Return the tail (basename) part of a path.
195
196def 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
203def 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
210def 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
217lexists = 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
222def 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
239def 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
275def 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
317def 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
398def 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.
451try:
452    from nt import _getfullpathname
453
454except 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
465else:  # 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
481realpath = abspath
482# Win9x family and earlier have no Unicode filename support.
483supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
484                              sys.getwindowsversion()[3] >= 2)
485
486def _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
494def 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