24
24
from genericpath import *
25
25
26
26
27
- __all__ = ["normcase" ,"isabs" ,"join" ,"splitdrive" ,"split" ,"splitext" ,
27
+ __all__ = ["normcase" ,"isabs" ,"join" ,"splitdrive" ,"splitroot" , " split" ,"splitext" ,
28
28
"basename" ,"dirname" ,"commonprefix" ,"getsize" ,"getmtime" ,
29
29
"getatime" ,"getctime" , "islink" ,"exists" ,"lexists" ,"isdir" ,"isfile" ,
30
30
"ismount" , "expanduser" ,"expandvars" ,"normpath" ,"abspath" ,
@@ -117,19 +117,21 @@ def join(path, *paths):
117
117
try :
118
118
if not paths :
119
119
path [:0 ] + sep #23780: Ensure compatible data type even if p is null.
120
- result_drive , result_path = splitdrive (path )
120
+ result_drive , result_root , result_path = splitroot (path )
121
121
for p in map (os .fspath , paths ):
122
- p_drive , p_path = splitdrive (p )
123
- if p_path and p_path [ 0 ] in seps :
122
+ p_drive , p_root , p_path = splitroot (p )
123
+ if p_root :
124
124
# Second path is absolute
125
125
if p_drive or not result_drive :
126
126
result_drive = p_drive
127
+ result_root = p_root
127
128
result_path = p_path
128
129
continue
129
130
elif p_drive and p_drive != result_drive :
130
131
if p_drive .lower () != result_drive .lower ():
131
132
# Different drives => ignore the first path entirely
132
133
result_drive = p_drive
134
+ result_root = p_root
133
135
result_path = p_path
134
136
continue
135
137
# Same drive in different case
@@ -139,10 +141,10 @@ def join(path, *paths):
139
141
result_path = result_path + sep
140
142
result_path = result_path + p_path
141
143
## add separator between UNC and non-absolute path
142
- if (result_path and result_path [ 0 ] not in seps and
144
+ if (result_path and not result_root and
143
145
result_drive and result_drive [- 1 :] != colon ):
144
146
return result_drive + sep + result_path
145
- return result_drive + result_path
147
+ return result_drive + result_root + result_path
146
148
except (TypeError , AttributeError , BytesWarning ):
147
149
genericpath ._check_arg_types ('join' , path , * paths )
148
150
raise
@@ -169,35 +171,61 @@ def splitdrive(p):
169
171
170
172
Paths cannot contain both a drive letter and a UNC path.
171
173
174
+ """
175
+ drive , root , tail = splitroot (p )
176
+ return drive , root + tail
177
+
178
+
179
+ def splitroot (p ):
180
+ """Split a pathname into drive, root and tail. The drive is defined
181
+ exactly as in splitdrive(). On Windows, the root may be a single path
182
+ separator or an empty string. The tail contains anything after the root.
183
+ For example:
184
+
185
+ splitroot('//server/share/') == ('//server/share', '/', '')
186
+ splitroot('C:/Users/Barney') == ('C:', '/', 'Users/Barney')
187
+ splitroot('C:///spam///ham') == ('C:', '/', '//spam///ham')
188
+ splitroot('Windows/notepad') == ('', '', 'Windows/notepad')
172
189
"""
173
190
p = os .fspath (p )
174
- if len (p ) >= 2 :
175
- if isinstance (p , bytes ):
176
- sep = b'\\ '
177
- altsep = b'/'
178
- colon = b':'
179
- unc_prefix = b'\\ \\ ?\\ UNC\\ '
180
- else :
181
- sep = '\\ '
182
- altsep = '/'
183
- colon = ':'
184
- unc_prefix = '\\ \\ ?\\ UNC\\ '
185
- normp = p .replace (altsep , sep )
186
- if normp [0 :2 ] == sep * 2 :
191
+ if isinstance (p , bytes ):
192
+ sep = b'\\ '
193
+ altsep = b'/'
194
+ colon = b':'
195
+ unc_prefix = b'\\ \\ ?\\ UNC\\ '
196
+ empty = b''
197
+ else :
198
+ sep = '\\ '
199
+ altsep = '/'
200
+ colon = ':'
201
+ unc_prefix = '\\ \\ ?\\ UNC\\ '
202
+ empty = ''
203
+ normp = p .replace (altsep , sep )
204
+ if normp [:1 ] == sep :
205
+ if normp [1 :2 ] == sep :
187
206
# UNC drives, e.g. \\server\share or \\?\UNC\server\share
188
207
# Device drives, e.g. \\.\device or \\?\device
189
208
start = 8 if normp [:8 ].upper () == unc_prefix else 2
190
209
index = normp .find (sep , start )
191
210
if index == - 1 :
192
- return p , p [: 0 ]
211
+ return p , empty , empty
193
212
index2 = normp .find (sep , index + 1 )
194
213
if index2 == - 1 :
195
- return p , p [:0 ]
196
- return p [:index2 ], p [index2 :]
197
- if normp [1 :2 ] == colon :
198
- # Drive-letter drives, e.g. X:
199
- return p [:2 ], p [2 :]
200
- return p [:0 ], p
214
+ return p , empty , empty
215
+ return p [:index2 ], p [index2 :index2 + 1 ], p [index2 + 1 :]
216
+ else :
217
+ # Relative path with root, e.g. \Windows
218
+ return empty , p [:1 ], p [1 :]
219
+ elif normp [1 :2 ] == colon :
220
+ if normp [2 :3 ] == sep :
221
+ # Absolute drive-letter path, e.g. X:\Windows
222
+ return p [:2 ], p [2 :3 ], p [3 :]
223
+ else :
224
+ # Relative path with drive, e.g. X:Windows
225
+ return p [:2 ], empty , p [2 :]
226
+ else :
227
+ # Relative path, e.g. Windows
228
+ return empty , empty , p
201
229
202
230
203
231
# Split a path in head (everything up to the last '/') and tail (the
@@ -212,15 +240,13 @@ def split(p):
212
240
Either part may be empty."""
213
241
p = os .fspath (p )
214
242
seps = _get_bothseps (p )
215
- d , p = splitdrive (p )
243
+ d , r , p = splitroot (p )
216
244
# set i to index beyond p's last slash
217
245
i = len (p )
218
246
while i and p [i - 1 ] not in seps :
219
247
i -= 1
220
248
head , tail = p [:i ], p [i :] # now tail has no slashes
221
- # remove trailing slashes from head, unless it's all slashes
222
- head = head .rstrip (seps ) or head
223
- return d + head , tail
249
+ return d + r + head .rstrip (seps ), tail
224
250
225
251
226
252
# Split a path in root and extension.
@@ -311,10 +337,10 @@ def ismount(path):
311
337
path = os .fspath (path )
312
338
seps = _get_bothseps (path )
313
339
path = abspath (path )
314
- root , rest = splitdrive (path )
315
- if root and root [0 ] in seps :
316
- return ( not rest ) or ( rest in seps )
317
- if rest and rest in seps :
340
+ drive , root , rest = splitroot (path )
341
+ if drive and drive [0 ] in seps :
342
+ return not rest
343
+ if root and not rest :
318
344
return True
319
345
320
346
if _getvolumepathname :
@@ -525,13 +551,8 @@ def normpath(path):
525
551
curdir = '.'
526
552
pardir = '..'
527
553
path = path .replace (altsep , sep )
528
- prefix , path = splitdrive (path )
529
-
530
- # collapse initial backslashes
531
- if path .startswith (sep ):
532
- prefix += sep
533
- path = path .lstrip (sep )
534
-
554
+ drive , root , path = splitroot (path )
555
+ prefix = drive + root
535
556
comps = path .split (sep )
536
557
i = 0
537
558
while i < len (comps ):
@@ -541,7 +562,7 @@ def normpath(path):
541
562
if i > 0 and comps [i - 1 ] != pardir :
542
563
del comps [i - 1 :i + 1 ]
543
564
i -= 1
544
- elif i == 0 and prefix . endswith ( sep ) :
565
+ elif i == 0 and root :
545
566
del comps [i ]
546
567
else :
547
568
i += 1
@@ -765,8 +786,8 @@ def relpath(path, start=None):
765
786
try :
766
787
start_abs = abspath (normpath (start ))
767
788
path_abs = abspath (normpath (path ))
768
- start_drive , start_rest = splitdrive (start_abs )
769
- path_drive , path_rest = splitdrive (path_abs )
789
+ start_drive , _ , start_rest = splitroot (start_abs )
790
+ path_drive , _ , path_rest = splitroot (path_abs )
770
791
if normcase (start_drive ) != normcase (path_drive ):
771
792
raise ValueError ("path is on mount %r, start on mount %r" % (
772
793
path_drive , start_drive ))
@@ -816,21 +837,19 @@ def commonpath(paths):
816
837
curdir = '.'
817
838
818
839
try :
819
- drivesplits = [splitdrive (p .replace (altsep , sep ).lower ()) for p in paths ]
820
- split_paths = [p .split (sep ) for d , p in drivesplits ]
840
+ drivesplits = [splitroot (p .replace (altsep , sep ).lower ()) for p in paths ]
841
+ split_paths = [p .split (sep ) for d , r , p in drivesplits ]
821
842
822
- try :
823
- isabs , = set (p [:1 ] == sep for d , p in drivesplits )
824
- except ValueError :
825
- raise ValueError ("Can't mix absolute and relative paths" ) from None
843
+ if len ({r for d , r , p in drivesplits }) != 1 :
844
+ raise ValueError ("Can't mix absolute and relative paths" )
826
845
827
846
# Check that all drive letters or UNC paths match. The check is made only
828
847
# now otherwise type errors for mixing strings and bytes would not be
829
848
# caught.
830
- if len (set ( d for d , p in drivesplits ) ) != 1 :
849
+ if len ({ d for d , r , p in drivesplits } ) != 1 :
831
850
raise ValueError ("Paths don't have the same drive" )
832
851
833
- drive , path = splitdrive (paths [0 ].replace (altsep , sep ))
852
+ drive , root , path = splitroot (paths [0 ].replace (altsep , sep ))
834
853
common = path .split (sep )
835
854
common = [c for c in common if c and c != curdir ]
836
855
@@ -844,8 +863,7 @@ def commonpath(paths):
844
863
else :
845
864
common = common [:len (s1 )]
846
865
847
- prefix = drive + sep if isabs else drive
848
- return prefix + sep .join (common )
866
+ return drive + root + sep .join (common )
849
867
except (TypeError , AttributeError ):
850
868
genericpath ._check_arg_types ('commonpath' , * paths )
851
869
raise
0 commit comments