1717 "ismount" ,"expanduser" ,"expandvars" ,"normpath" ,"abspath" ,
1818 "curdir" ,"pardir" ,"sep" ,"pathsep" ,"defpath" ,"altsep" ,
1919 "extsep" ,"devnull" ,"realpath" ,"supports_unicode_filenames" ,"relpath" ,
20- "samefile" ,"sameopenfile" ,"samestat" , "commonpath" , "splitunc" ]
20+ "samefile" ,"sameopenfile" ,"samestat" , "commonpath" ]
2121
2222# strings representing various path-related bits and pieces
23+ # These are primarily for export; internally, they are hardcoded.
24+ # Should be set before imports for resolving cyclic dependency.
2325curdir = '.'
2426pardir = '..'
2527extsep = '.'
2931defpath = '.;' + (os .environ ['UNIXROOT' ] + '\\ usr\\ bin' if 'UNIXROOT' in os .environ else 'C:\\ bin' )
3032devnull = 'nul'
3133
32- # Normalize the case of a pathname and map slashes to backslashes.
34+ def _get_bothseps (path ):
35+ if isinstance (path , bytes ):
36+ return b'\\ /'
37+ else :
38+ return '\\ /'
39+
40+ # Normalize the case of a pathname and map altseps to seps.
3341# Other normalizations (such as optimizing '../' away) are not done
3442# (this is done by normpath).
3543
3644def normcase (s ):
3745 """Normalize case of pathname.
3846
3947 Makes all characters lowercase and all altseps into seps."""
48+ s = os .fspath (s )
49+ if isinstance (s , bytes ):
50+ sep = b'/'
51+ altsep = b'\\ '
52+ else :
53+ sep = '/'
54+ altsep = '\\ '
4055 return s .replace (altsep , sep ).lower ()
4156
4257
4358# Join two (or more) paths.
4459
4560def join (a , * p ):
4661 """Join two or more pathname components, inserting sep as needed.
47-
4862 Also replaces all altsep chars with sep in the returned string
4963 to make it consistent."""
64+ a = os .fspath (a )
5065 path = a
51- for b in p :
52- if isabs (b ):
53- path = b
54- elif path == '' or path [- 1 :] in '/\\ :' :
55- path = path + b
56- else :
57- path = path + '/' + b
58- return path .replace (altsep , sep )
5966
67+ if isinstance (path , bytes ):
68+ sep = b'/'
69+ seps = b'\\ /;'
70+ altsep = b'\\ '
71+ else :
72+ sep = '/'
73+ seps = '\\ /:'
74+ altsep = '\\ '
6075
61- # Parse UNC paths
62- def splitunc (p ):
63- """Split a pathname into UNC mount point and relative path specifiers.
64-
65- Return a 2-tuple (unc, rest); either part may be empty.
66- If unc is not empty, it has the form '//host/mount' (or similar
67- using backslashes). unc+rest is always the input path.
68- Paths containing drive letters never have an UNC part.
69- """
70- if p [1 :2 ] == ':' :
71- return '' , p # Drive letter present
72- firstTwo = p [0 :2 ]
73- if firstTwo == '/' * 2 or firstTwo == '\\ ' * 2 :
74- # is a UNC path:
75- # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
76- # \\machine\mountpoint\directories...
77- # directory ^^^^^^^^^^^^^^^
78- normp = normcase (p )
79- index = normp .find ('/' , 2 )
80- if index == - 1 :
81- ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
82- return ("" , p )
83- index = normp .find ('/' , index + 1 )
84- if index == - 1 :
85- index = len (p )
86- return p [:index ], p [index :]
87- return '' , p
76+ try :
77+ if not p :
78+ path [:0 ] + sep #23780: Ensure compatible data type even if p is null.
79+ for b in map (os .fspath , p ):
80+ if isabs (b ):
81+ path = b
82+ elif not path or path .endswith (seps ):
83+ path = path + b
84+ else :
85+ path = path + sep + b
86+ except (TypeError , AttributeError , BytesWarning ):
87+ genericpath ._check_arg_types ('join' , a , * p )
88+ raise
89+ return path .replace (altsep , sep )
8890
8991
9092# Return the tail (basename) part of a path.
@@ -107,39 +109,61 @@ def dirname(p):
107109# or an UNC path with at most a / or \ after the mount point.
108110
109111def ismount (path ):
110- """Test whether a path is a mount point (defined as root of drive)"""
111- unc , rest = splitunc (path )
112- if unc :
113- return rest in ("" , "/" , "\\ " )
114- p = splitdrive (path )[1 ]
115- return len (p ) == 1 and p [0 ] in '/\\ '
116-
112+ """Test whether a path is a mount point (a drive root, the root of a
113+ share, or a mounted volume)"""
114+ path = os .fspath (path )
115+ seps = _get_bothseps (path )
116+ path = abspath (path )
117+ root , rest = splitdrive (path )
118+ if root and root [0 ] in seps :
119+ return (not rest ) or (rest in seps )
120+ if rest in seps :
121+ return True
122+
123+ return False
117124
118125# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
119126
120127def normpath (path ):
121128 """Normalize path, eliminating double slashes, etc."""
122- path = str (path ).replace ('\\ ' , '/' )
129+ path = os .fspath (path )
130+ if isinstance (path , bytes ):
131+ sep = b'/'
132+ altsep = b'\\ '
133+ curdir = b'.'
134+ pardir = b'..'
135+ else :
136+ sep = '/'
137+ altsep = '\\ '
138+ curdir = '.'
139+ pardir = '..'
140+ path = path .replace (altsep , sep )
123141 prefix , path = splitdrive (path )
124- while path [:1 ] == '/' :
125- prefix = prefix + '/'
126- path = path [1 :]
127- comps = path .split ('/' )
142+
143+ # collapse initial backslashes
144+ if path .startswith (sep ):
145+ prefix += sep
146+ path = path .lstrip (sep )
147+
148+ comps = path .split (sep )
128149 i = 0
129150 while i < len (comps ):
130- if comps [i ] == '.' :
131- del comps [i ]
132- elif comps [i ] == '..' and i > 0 and comps [i - 1 ] not in ('' , '..' ):
133- del comps [i - 1 :i + 1 ]
134- i = i - 1
135- elif comps [i ] == '' and i > 0 and comps [i - 1 ] != '' :
151+ if not comps [i ] or comps [i ] == curdir :
136152 del comps [i ]
153+ elif comps [i ] == pardir :
154+ if i > 0 and comps [i - 1 ] != pardir :
155+ del comps [i - 1 :i + 1 ]
156+ i -= 1
157+ elif i == 0 and prefix .endswith (sep ):
158+ del comps [i ]
159+ else :
160+ i += 1
137161 else :
138- i = i + 1
162+ i += 1
139163 # If the path is now empty, substitute '.'
140164 if not prefix and not comps :
141- comps .append ('.' )
142- return prefix + '/' .join (comps )
165+ comps .append (curdir )
166+ return prefix + sep .join (comps )
143167
144168
145169# Return an absolute path.
@@ -250,38 +274,51 @@ def samestat(s1, s2):
250274supports_unicode_filenames = False
251275
252276
253- def relpath (path , start = curdir ):
277+ def relpath (path , start = None ):
254278 """Return a relative version of a path"""
279+ path = os .fspath (path )
280+ if isinstance (path , bytes ):
281+ sep = b'\\ '
282+ curdir = b'.'
283+ pardir = b'..'
284+ else :
285+ sep = '\\ '
286+ curdir = '.'
287+ pardir = '..'
255288
256289 if not path :
257290 raise ValueError ("no path specified" )
258- start_list = abspath (start ).split (sep )
259- path_list = abspath (path ).split (sep )
260- # Remove empty components after trailing slashes
261- if (start_list [- 1 ] == '' ):
262- start_list .pop ()
263- if (path_list [- 1 ] == '' ):
264- path_list .pop ()
265- if start_list [0 ].lower () != path_list [0 ].lower ():
266- unc_path , rest = splitunc (path )
267- unc_start , rest = splitunc (start )
268- if bool (unc_path ) ^ bool (unc_start ):
269- raise ValueError ("Cannot mix UNC and non-UNC paths (%s and %s)"
270- % (path , start ))
271- else :
272- raise ValueError ("path is on drive %s, start on drive %s"
273- % (path_list [0 ], start_list [0 ]))
274- # Work out how much of the filepath is shared by start and path.
275- for i in range (min (len (start_list ), len (path_list ))):
276- if start_list [i ].lower () != path_list [i ].lower ():
277- break
291+
292+ if start is None :
293+ start = curdir
278294 else :
279- i += 1
295+ start = os . fspath ( start )
280296
281- rel_list = [pardir ] * (len (start_list )- i ) + path_list [i :]
282- if not rel_list :
283- return curdir
284- return join (* rel_list )
297+ try :
298+ start_abs = abspath (start )
299+ path_abs = abspath (path )
300+ start_drive , start_rest = splitdrive (start_abs )
301+ path_drive , path_rest = splitdrive (path_abs )
302+ if normcase (start_drive ) != normcase (path_drive ):
303+ raise ValueError ("path is on mount %r, start on mount %r" % (
304+ path_drive , start_drive ))
305+
306+ start_list = start_rest .split (sep ) if start_rest else []
307+ path_list = path_rest .split (sep ) if path_list else []
308+ # Work out how much of the filepath is shared by start and path.
309+ i = 0
310+ for e1 , e2 in zip (start_list , path_list ):
311+ if normcase (e1 ) != normcase (e2 ):
312+ break
313+ i += 1
314+
315+ rel_list = [pardir ] * (len (start_list )- i ) + path_list [i :]
316+ if not rel_list :
317+ return curdir
318+ return join (* rel_list )
319+ except (TypeError , ValueError , AttributeError , BytesWarning , DeprecationWarning ):
320+ genericpath ._check_arg_types ('relpath' , path , start )
321+ raise
285322
286323# Return the longest common sub-path of the sequence of paths given as input.
287324# The function is case-insensitive and 'separator-insensitive', i.e. if the
0 commit comments