1"""Pathname and path-related operations for the Macintosh."""
2
3# strings representing various path-related bits and pieces
4# These are primarily for export; internally, they are hardcoded.
5# Should be set before imports for resolving cyclic dependency.
6curdir = ':'
7pardir = '::'
8extsep = '.'
9sep = ':'
10pathsep = '\n'
11defpath = ':'
12altsep = None
13devnull = 'Dev:Null'
14
15import os
16from stat import *
17import genericpath
18from genericpath import *
19import warnings
20
21warnings.warn('the macpath module is deprecated in 3.7 and will be removed '
22              'in 3.8', DeprecationWarning, stacklevel=2)
23
24__all__ = ["normcase","isabs","join","splitdrive","split","splitext",
25           "basename","dirname","commonprefix","getsize","getmtime",
26           "getatime","getctime", "islink","exists","lexists","isdir","isfile",
27           "expanduser","expandvars","normpath","abspath",
28           "curdir","pardir","sep","pathsep","defpath","altsep","extsep",
29           "devnull","realpath","supports_unicode_filenames"]
30
31def _get_colon(path):
32    if isinstance(path, bytes):
33        return b':'
34    else:
35        return ':'
36
37# Normalize the case of a pathname.  Dummy in Posix, but <s>.lower() here.
38
39def normcase(path):
40    if not isinstance(path, (bytes, str)):
41        raise TypeError("normcase() argument must be str or bytes, "
42                        "not '{}'".format(path.__class__.__name__))
43    return path.lower()
44
45
46def isabs(s):
47    """Return true if a path is absolute.
48    On the Mac, relative paths begin with a colon,
49    but as a special case, paths with no colons at all are also relative.
50    Anything else is absolute (the string up to the first colon is the
51    volume name)."""
52
53    colon = _get_colon(s)
54    return colon in s and s[:1] != colon
55
56
57def join(s, *p):
58    try:
59        colon = _get_colon(s)
60        path = s
61        if not p:
62            path[:0] + colon  #23780: Ensure compatible data type even if p is null.
63        for t in p:
64            if (not path) or isabs(t):
65                path = t
66                continue
67            if t[:1] == colon:
68                t = t[1:]
69            if colon not in path:
70                path = colon + path
71            if path[-1:] != colon:
72                path = path + colon
73            path = path + t
74        return path
75    except (TypeError, AttributeError, BytesWarning):
76        genericpath._check_arg_types('join', s, *p)
77        raise
78
79
80def split(s):
81    """Split a pathname into two parts: the directory leading up to the final
82    bit, and the basename (the filename, without colons, in that directory).
83    The result (s, t) is such that join(s, t) yields the original argument."""
84
85    colon = _get_colon(s)
86    if colon not in s: return s[:0], s
87    col = 0
88    for i in range(len(s)):
89        if s[i:i+1] == colon: col = i + 1
90    path, file = s[:col-1], s[col:]
91    if path and not colon in path:
92        path = path + colon
93    return path, file
94
95
96def splitext(p):
97    if isinstance(p, bytes):
98        return genericpath._splitext(p, b':', altsep, b'.')
99    else:
100        return genericpath._splitext(p, sep, altsep, extsep)
101splitext.__doc__ = genericpath._splitext.__doc__
102
103def splitdrive(p):
104    """Split a pathname into a drive specification and the rest of the
105    path.  Useful on DOS/Windows/NT; on the Mac, the drive is always
106    empty (don't use the volume name -- it doesn't have the same
107    syntactic and semantic oddities as DOS drive letters, such as there
108    being a separate current directory per drive)."""
109
110    return p[:0], p
111
112
113# Short interfaces to split()
114
115def dirname(s): return split(s)[0]
116def basename(s): return split(s)[1]
117
118def ismount(s):
119    if not isabs(s):
120        return False
121    components = split(s)
122    return len(components) == 2 and not components[1]
123
124def islink(s):
125    """Return true if the pathname refers to a symbolic link."""
126
127    try:
128        import Carbon.File
129        return Carbon.File.ResolveAliasFile(s, 0)[2]
130    except:
131        return False
132
133# Is `stat`/`lstat` a meaningful difference on the Mac?  This is safe in any
134# case.
135
136def lexists(path):
137    """Test whether a path exists.  Returns True for broken symbolic links"""
138
139    try:
140        st = os.lstat(path)
141    except OSError:
142        return False
143    return True
144
145def expandvars(path):
146    """Dummy to retain interface-compatibility with other operating systems."""
147    return path
148
149
150def expanduser(path):
151    """Dummy to retain interface-compatibility with other operating systems."""
152    return path
153
154class norm_error(Exception):
155    """Path cannot be normalized"""
156
157def normpath(s):
158    """Normalize a pathname.  Will return the same result for
159    equivalent paths."""
160
161    colon = _get_colon(s)
162
163    if colon not in s:
164        return colon + s
165
166    comps = s.split(colon)
167    i = 1
168    while i < len(comps)-1:
169        if not comps[i] and comps[i-1]:
170            if i > 1:
171                del comps[i-1:i+1]
172                i = i - 1
173            else:
174                # best way to handle this is to raise an exception
175                raise norm_error('Cannot use :: immediately after volume name')
176        else:
177            i = i + 1
178
179    s = colon.join(comps)
180
181    # remove trailing ":" except for ":" and "Volume:"
182    if s[-1:] == colon and len(comps) > 2 and s != colon*len(s):
183        s = s[:-1]
184    return s
185
186def abspath(path):
187    """Return an absolute path."""
188    if not isabs(path):
189        if isinstance(path, bytes):
190            cwd = os.getcwdb()
191        else:
192            cwd = os.getcwd()
193        path = join(cwd, path)
194    return normpath(path)
195
196# realpath is a no-op on systems without islink support
197def realpath(path):
198    path = abspath(path)
199    try:
200        import Carbon.File
201    except ImportError:
202        return path
203    if not path:
204        return path
205    colon = _get_colon(path)
206    components = path.split(colon)
207    path = components[0] + colon
208    for c in components[1:]:
209        path = join(path, c)
210        try:
211            path = Carbon.File.FSResolveAliasFile(path, 1)[0].as_pathname()
212        except Carbon.File.Error:
213            pass
214    return path
215
216supports_unicode_filenames = True
217