2626# Build OS-portable and safer paths
2727
2828
29- def safe_path (path , posix = False , preserve_spaces = False ):
29+ def safe_path (path , posix = False , preserve_spaces = False , posix_only = False ):
3030 """
3131 Convert `path` to a safe and portable POSIX path usable on multiple OSes.
3232 The returned path is an ASCII-only byte string, resolved for relative
@@ -52,7 +52,13 @@ def safe_path(path, posix=False, preserve_spaces=False):
5252 _pathmod , path_sep = path_handlers (path , posix )
5353
5454 segments = [s .strip () for s in path .split (path_sep ) if s .strip ()]
55- segments = [portable_filename (s , preserve_spaces = preserve_spaces ) for s in segments ]
55+ segments = [
56+ portable_filename (
57+ s ,
58+ preserve_spaces = preserve_spaces ,
59+ posix_only = posix_only
60+ ) for s in segments
61+ ]
5662
5763 if not segments :
5864 return '_'
@@ -134,17 +140,34 @@ def resolve(path, posix=True):
134140 return path
135141
136142
137- legal_punctuation = r"!\#$%&\(\)\+,\-\.;\=@\[\]_\{\}\~"
138- legal_spaces = r" "
139- legal_chars = r'A-Za-z0-9' + legal_punctuation
143+ legal_punctuation = r'!\#$%&\(\)\+,\-\.;\=@\[\]_\{\}\~'
144+ legal_spaces = r' '
145+ legal_alphanumeric = r'A-Za-z0-9'
146+ legal_chars = legal_alphanumeric + legal_punctuation
140147legal_chars_inc_spaces = legal_chars + legal_spaces
141148illegal_chars_re = r'[^' + legal_chars + r']'
142149illegal_chars_exc_spaces_re = r'[^' + legal_chars_inc_spaces + r']'
143150replace_illegal_chars = re .compile (illegal_chars_re ).sub
144151replace_illegal_chars_exc_spaces = re .compile (illegal_chars_exc_spaces_re ).sub
145152
146153
147- def portable_filename (filename , preserve_spaces = False ):
154+ posix_legal_punctuation = r'<:"/>\|\*\^\\\'`\?' + legal_punctuation
155+ posix_legal_chars = legal_alphanumeric + posix_legal_punctuation
156+ posix_legal_chars_inc_spaces = posix_legal_chars + legal_spaces
157+ posix_illegal_chars_re = r'[^' + posix_legal_chars + r']'
158+ posix_illegal_chars_exc_spaces_re = r'[^' + posix_legal_chars_inc_spaces + r']'
159+ replace_illegal_posix_chars = re .compile (posix_illegal_chars_re ).sub
160+ replace_illegal_posix_chars_exc_spaces = re .compile (posix_illegal_chars_exc_spaces_re ).sub
161+
162+
163+ ILLEGAL_WINDOWS_NAMES = set ([
164+ 'com1' , 'com2' , 'com3' , 'com4' , 'com5' , 'com6' , 'com7' , 'com8' , 'com9' ,
165+ 'lpt1' , 'lpt2' , 'lpt3' , 'lpt4' , 'lpt5' , 'lpt6' , 'lpt7' , 'lpt8' , 'lpt9' ,
166+ 'aux' , 'con' , 'nul' , 'prn'
167+ ])
168+
169+
170+ def portable_filename (filename , preserve_spaces = False , posix_only = False ):
148171 """
149172 Return a new name for `filename` that is portable across operating systems.
150173
@@ -170,22 +193,21 @@ def portable_filename(filename, preserve_spaces=False):
170193 if not filename :
171194 return '_'
172195
173- if preserve_spaces :
174- filename = replace_illegal_chars_exc_spaces ('_' , filename )
196+ if posix_only :
197+ if preserve_spaces :
198+ filename = replace_illegal_posix_chars_exc_spaces ('_' , filename )
199+ else :
200+ filename = replace_illegal_posix_chars ('_' , filename )
175201 else :
176- filename = replace_illegal_chars ('_' , filename )
177-
178- # these are illegal both upper and lowercase and with or without an extension
179- # we insert an underscore after the base name.
180- windows_illegal_names = set ([
181- 'com1' , 'com2' , 'com3' , 'com4' , 'com5' , 'com6' , 'com7' , 'com8' , 'com9' ,
182- 'lpt1' , 'lpt2' , 'lpt3' , 'lpt4' , 'lpt5' , 'lpt6' , 'lpt7' , 'lpt8' , 'lpt9' ,
183- 'aux' , 'con' , 'nul' , 'prn'
184- ])
202+ if preserve_spaces :
203+ filename = replace_illegal_chars_exc_spaces ('_' , filename )
204+ else :
205+ filename = replace_illegal_chars ('_' , filename )
185206
186- basename , dot , extension = filename .partition ('.' )
187- if basename .lower () in windows_illegal_names :
188- filename = '' .join ([basename , '_' , dot , extension ])
207+ if not posix_only :
208+ basename , dot , extension = filename .partition ('.' )
209+ if basename .lower () in ILLEGAL_WINDOWS_NAMES :
210+ filename = '' .join ([basename , '_' , dot , extension ])
189211
190212 # no name made only of dots.
191213 if set (filename ) == set (['.' ]):
@@ -198,6 +220,7 @@ def portable_filename(filename, preserve_spaces=False):
198220
199221 return filename
200222
223+
201224#
202225# paths comparisons, common prefix and suffix extraction
203226#
0 commit comments