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    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
136def 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
169def 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
194def splitext(p):
195    return genericpath._splitext(p, sep, altsep, extsep)
196splitext.__doc__ = genericpath._splitext.__doc__
197
198
199# Return the tail (basename) part of a path.
200
201def 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
208def 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
215def 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
222lexists = 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
227def 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
244def 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
280def 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
322def 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
403def 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.
456try:
457    from nt import _getfullpathname
458
459except 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
470else:  # 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
486realpath = abspath
487# Win9x family and earlier have no Unicode filename support.
488supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
489                              sys.getwindowsversion()[3] >= 2)
490
491def _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
499def 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