33import logging
44import os
55import os .path as osp
6- import re
76import shutil
87import stat
98import tempfile
1615logger = logging .getLogger ("fsspec.local" )
1716
1817
18+ def _remove_prefix (text : str , prefix : str ):
19+ if text .startswith (prefix ):
20+ return text [len (prefix ) :]
21+ return text
22+
23+
1924class LocalFileSystem (AbstractFileSystem ):
2025 """Interface to files on local storage
2126
@@ -116,8 +121,8 @@ def lexists(self, path, **kwargs):
116121 return osp .lexists (path )
117122
118123 def cp_file (self , path1 , path2 , ** kwargs ):
119- path1 = self ._strip_protocol (path1 ). rstrip ( "/" )
120- path2 = self ._strip_protocol (path2 ). rstrip ( "/" )
124+ path1 = self ._strip_protocol (path1 , remove_trailing_slash = True )
125+ path2 = self ._strip_protocol (path2 , remove_trailing_slash = True )
121126 if self .auto_mkdir :
122127 self .makedirs (self ._parent (path2 ), exist_ok = True )
123128 if self .isfile (path1 ):
@@ -138,8 +143,8 @@ def put_file(self, path1, path2, callback=None, **kwargs):
138143 return self .cp_file (path1 , path2 , ** kwargs )
139144
140145 def mv_file (self , path1 , path2 , ** kwargs ):
141- path1 = self ._strip_protocol (path1 ). rstrip ( "/" )
142- path2 = self ._strip_protocol (path2 ). rstrip ( "/" )
146+ path1 = self ._strip_protocol (path1 , remove_trailing_slash = True )
147+ path2 = self ._strip_protocol (path2 , remove_trailing_slash = True )
143148 shutil .move (path1 , path2 )
144149
145150 def link (self , src , dst , ** kwargs ):
@@ -163,7 +168,7 @@ def rm(self, path, recursive=False, maxdepth=None):
163168 path = [path ]
164169
165170 for p in path :
166- p = self ._strip_protocol (p ). rstrip ( "/" )
171+ p = self ._strip_protocol (p , remove_trailing_slash = True )
167172 if self .isdir (p ):
168173 if not recursive :
169174 raise ValueError ("Cannot delete directory, set recursive=True" )
@@ -206,24 +211,32 @@ def modified(self, path):
206211
207212 @classmethod
208213 def _parent (cls , path ):
209- path = cls ._strip_protocol (path ).rstrip ("/" )
210- if "/" in path :
211- return path .rsplit ("/" , 1 )[0 ]
214+ path = cls ._strip_protocol (path , remove_trailing_slash = True )
215+ if os .sep == "/" :
216+ # posix native
217+ return path .rsplit ("/" , 1 )[0 ] or "/"
212218 else :
213- return cls .root_marker
219+ # NT
220+ path_ = path .rsplit ("/" , 1 )[0 ]
221+ if len (path_ ) <= 3 :
222+ if path_ [1 :2 ] == ":" :
223+ # nt root (something like c:/)
224+ return path_ [0 ] + ":/"
225+ # More cases may be required here
226+ return path_
214227
215228 @classmethod
216- def _strip_protocol (cls , path ):
229+ def _strip_protocol (cls , path , remove_trailing_slash = False ):
217230 path = stringify_path (path )
218- if path .startswith ("file://" ):
219- path = path [7 :]
220- elif path .startswith ("file:" ):
221- path = path [5 :]
222- elif path .startswith ("local://" ):
223- path = path [8 :]
231+ if path .startswith ("file:" ):
232+ path = _remove_prefix (_remove_prefix (path , "file://" ), "file:" )
233+ if os .sep == "\\ " :
234+ path = path .lstrip ("/" )
224235 elif path .startswith ("local:" ):
225- path = path [6 :]
226- return make_path_posix (path ).rstrip ("/" ) or cls .root_marker
236+ path = _remove_prefix (_remove_prefix (path , "local://" ), "local:" )
237+ if os .sep == "\\ " :
238+ path = path .lstrip ("/" )
239+ return make_path_posix (path , remove_trailing_slash )
227240
228241 def _isfilestore (self ):
229242 # Inheriting from DaskFileSystem makes this False (S3, etc. were)
@@ -236,47 +249,42 @@ def chmod(self, path, mode):
236249 return os .chmod (path , mode )
237250
238251
239- def make_path_posix (path , sep = os .sep ):
240- """Make path generic"""
241- if isinstance (path , (list , set , tuple )):
242- return type (path )(make_path_posix (p ) for p in path )
243- if "~" in path :
244- path = osp .expanduser (path )
245- if sep == "/" :
246- # most common fast case for posix
252+ def make_path_posix (path , remove_trailing_slash = False ):
253+ """Make path generic for current OS"""
254+ if not isinstance (path , str ):
255+ if isinstance (path , (list , set , tuple )):
256+ return type (path )(make_path_posix (p , remove_trailing_slash ) for p in path )
257+ else :
258+ path = str (stringify_path (path ))
259+ if os .sep == "/" :
260+ # Native posix
247261 if path .startswith ("/" ):
248- return path
249- if path .startswith ("./" ):
262+ # most common fast case for posix
263+ return path .rstrip ("/" ) or "/" if remove_trailing_slash else path
264+ elif path .startswith ("~" ):
265+ return make_path_posix (osp .expanduser (path ), remove_trailing_slash )
266+ elif path .startswith ("./" ):
250267 path = path [2 :]
268+ path = f"{ os .getcwd ()} /{ path } "
269+ return path .rstrip ("/" ) or "/" if remove_trailing_slash else path
251270 return f"{ os .getcwd ()} /{ path } "
252- if (
253- (sep not in path and "/" not in path )
254- or (sep == "/" and not path .startswith ("/" ))
255- or (sep == "\\ " and ":" not in path and not path .startswith ("\\ \\ " ))
256- ):
257- # relative path like "path" or "rel\\path" (win) or rel/path"
258- if os .sep == "\\ " :
259- # abspath made some more '\\' separators
260- return make_path_posix (osp .abspath (path ))
261- else :
262- return f"{ os .getcwd ()} /{ path } "
263- if path .startswith ("file://" ):
264- path = path [7 :]
265- if re .match ("/[A-Za-z]:" , path ):
266- # for windows file URI like "file:///C:/folder/file"
267- # or "file:///C:\\dir\\file"
268- path = path [1 :].replace ("\\ " , "/" ).replace ("//" , "/" )
269- if path .startswith ("\\ \\ " ):
270- # special case for windows UNC/DFS-style paths, do nothing,
271- # just flip the slashes around (case below does not work!)
272- return path .replace ("\\ " , "/" )
273- if re .match ("[A-Za-z]:" , path ):
274- # windows full path like "C:\\local\\path"
275- return path .lstrip ("\\ " ).replace ("\\ " , "/" ).replace ("//" , "/" )
276- if path .startswith ("\\ " ):
277- # windows network path like "\\server\\path"
278- return "/" + path .lstrip ("\\ " ).replace ("\\ " , "/" ).replace ("//" , "/" )
279- return path
271+ else :
272+ # NT handling
273+ if len (path ) > 1 :
274+ if path [1 ] == ":" :
275+ # windows full path like "C:\\local\\path"
276+ if len (path ) <= 3 :
277+ # nt root (something like c:/)
278+ return path [0 ] + ":/"
279+ path = path .replace ("\\ " , "/" ).replace ("//" , "/" )
280+ return path .rstrip ("/" ) if remove_trailing_slash else path
281+ elif path [0 ] == "~" :
282+ return make_path_posix (osp .expanduser (path ), remove_trailing_slash )
283+ elif path .startswith (("\\ \\ " , "//" )):
284+ # windows UNC/DFS-style paths
285+ path = "//" + path [2 :].replace ("\\ " , "/" ).replace ("//" , "/" )
286+ return path .rstrip ("/" ) if remove_trailing_slash else path
287+ return make_path_posix (osp .abspath (path ), remove_trailing_slash )
280288
281289
282290def trailing_sep (path ):
0 commit comments