Skip to content

Commit 430cf83

Browse files
authored
Merge pull request #26 from nexB/preserve_spaces_in_filenames
Add preserve_spaces argument to portable_filename
2 parents 452c2b5 + a78a89b commit 430cf83

3 files changed

Lines changed: 23 additions & 5 deletions

File tree

CHANGELOG.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Release notes
44
vNext
55
-----
66

7+
- Add preserve_spaces argument in commoncode.paths.portable_filename.
8+
This argument will prevent the replacement of spaces in filenames.
9+
710

811
Version 21.6.11
912
---------------
@@ -32,7 +35,7 @@ Version 21.5.12
3235

3336
- Add new function to find a command or shared object file in the PATH (e.g. in
3437
environment variables). See commoncode.command.find_in_path()
35-
- Add new simplified the commoncode.command.execute() function.
38+
- Add new simplified the commoncode.command.execute() function.
3639
- Add support for Python 3.10
3740
- Update tests to cope with Python 3.6 bug https://bugs.python.org/issue26919
3841
- Adopt latest skeleton with configure scripts updates

src/commoncode/paths.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@
2626
# Build OS-portable and safer paths
2727

2828

29-
def safe_path(path, posix=False):
29+
def safe_path(path, posix=False, preserve_spaces=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
3333
segments and itself relative.
3434
3535
The `path` is treated as a POSIX path if `posix` is True or as a Windows
3636
path with blackslash separators otherwise.
37+
38+
If `preserve_spaces` is True, then the spaces in `path` will not be replaced.
3739
"""
3840
# if the path is UTF, try to use unicode instead
3941
if not isinstance(path, str):
@@ -50,7 +52,7 @@ def safe_path(path, posix=False):
5052
_pathmod, path_sep = path_handlers(path, posix)
5153

5254
segments = [s.strip() for s in path.split(path_sep) if s.strip()]
53-
segments = [portable_filename(s) for s in segments]
55+
segments = [portable_filename(s, preserve_spaces=preserve_spaces) for s in segments]
5456

5557
if not segments:
5658
return '_'
@@ -133,12 +135,16 @@ def resolve(path, posix=True):
133135

134136

135137
legal_punctuation = r"!\#$%&\(\)\+,\-\.;\=@\[\]_\{\}\~"
138+
legal_spaces = r" "
136139
legal_chars = r'A-Za-z0-9' + legal_punctuation
140+
legal_chars_inc_spaces = legal_chars + legal_spaces
137141
illegal_chars_re = r'[^' + legal_chars + r']'
142+
illegal_chars_exc_spaces_re = r'[^' + legal_chars_inc_spaces + r']'
138143
replace_illegal_chars = re.compile(illegal_chars_re).sub
144+
replace_illegal_chars_exc_spaces = re.compile(illegal_chars_exc_spaces_re).sub
139145

140146

141-
def portable_filename(filename):
147+
def portable_filename(filename, preserve_spaces=False):
142148
"""
143149
Return a new name for `filename` that is portable across operating systems.
144150
@@ -156,13 +162,18 @@ def portable_filename(filename):
156162
157163
Also inspired by Werkzeug:
158164
https://raw.githubusercontent.com/pallets/werkzeug/8c2d63ce247ba1345e1b9332a68ceff93b2c07ab/werkzeug/utils.py
165+
166+
If `preserve_spaces` is True, then spaces in `filename` will not be replaced.
159167
"""
160168
filename = toascii(filename, translit=True)
161169

162170
if not filename:
163171
return '_'
164172

165-
filename = replace_illegal_chars('_', filename)
173+
if preserve_spaces:
174+
filename = replace_illegal_chars_exc_spaces('_', filename)
175+
else:
176+
filename = replace_illegal_chars('_', filename)
166177

167178
# these are illegal both upper and lowercase and with or without an extension
168179
# we insert an underscore after the base name.

tests/test_paths.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ def test_portable_filename(self):
127127
expected = 'A___file__with_Spaces.mov'
128128
assert paths.portable_filename("A:\\ file/ with Spaces.mov") == expected
129129

130+
# Test `preserve_spaces` option. Spaces should not be replaced
131+
expected = 'Program Files (x86)'
132+
assert paths.portable_filename("Program Files (x86)", preserve_spaces=True) == expected
133+
130134
# Unresolved relative paths will be treated as a single filename. Use
131135
# resolve instead if you want to resolve paths:
132136
expected = '___.._.._etc_passwd'

0 commit comments

Comments
 (0)