diff --git a/HISTORY.rst b/HISTORY.rst index 0f981b5..e58ecb4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,8 @@ History master ^^^^^^ +- Quote Fish PATH entries so locations with spaces (e.g. ``Application Support``) are a single token + 1.9.2 ^^^^^ diff --git a/tests/test_fish.py b/tests/test_fish.py index be3fad4..a7b0018 100644 --- a/tests/test_fish.py +++ b/tests/test_fish.py @@ -63,3 +63,23 @@ def test_append_multiple(self, request, shell_test): stdout, stderr = process.communicate() assert process.returncode == 0, (stdout + stderr).decode('utf-8') + + +def test_fish_config_quotes_paths_with_spaces(): + from os import pathsep + + from userpath.shells import Fish + + fish = Fish(home='/tmp/home') + location = '/Users/hynek/Library/Application Support/hatch/bin' + contents = next(iter(fish.config(location, front=True).values())) + assert contents == 'set PATH "{}" $PATH'.format(location) + + contents = next(iter(fish.config(location, front=False).values())) + assert contents == 'set PATH $PATH "{}"'.format(location) + + locations = pathsep.join( + ['/foo/Application Support/bin', '/bar/My Apps/bin'] + ) + contents = next(iter(fish.config(locations, front=True).values())) + assert contents == 'set PATH "/foo/Application Support/bin" "/bar/My Apps/bin" $PATH' diff --git a/userpath/shells.py b/userpath/shells.py index 66114d4..919ad71 100644 --- a/userpath/shells.py +++ b/userpath/shells.py @@ -53,10 +53,22 @@ def show_path_commands(cls): return [['bash', '-i', '-c', 'echo $PATH'], ['bash', '-i', '-l', '-c', 'echo $PATH']] +def _fish_quote(location): + """Quote a path so fish treats it as a single token. + + Unquoted spaces in ``set PATH ...`` split the entry (fish-shell/fish-shell#527). + Double-quote and escape ``\\``, ``"``, and ``$`` so Hatch-style paths like + ``.../Application Support/...`` survive into config.fish. + """ + escaped = location.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + return '"{}"'.format(escaped) + + class Fish(Shell): def config(self, location, front=True): - location = ' '.join(location.split(pathsep)) - head, tail = (location, '$PATH') if front else ('$PATH', location) + # PATH is a list in fish; quote each entry so spaces are not word-split. + quoted = ' '.join(_fish_quote(part) for part in location.split(pathsep)) + head, tail = (quoted, '$PATH') if front else ('$PATH', quoted) # https://github.com/fish-shell/fish-shell/issues/527#issuecomment-12436286 contents = 'set PATH {} {}'.format(head, tail)