Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
default_language_version:
python: python3.15
python: python3
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
Expand Down
2 changes: 2 additions & 0 deletions docs/changelog/+quoted-user-dir-backslashes.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Preserve literal backslashes before ordinary characters in double-quoted ``user-dirs.dirs`` paths instead of removing
them.
11 changes: 6 additions & 5 deletions docs/platforms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,12 @@ See also: :ref:`api:User binary directory`

See also: :ref:`api:User documents directory`

On Unix, use percent signs as literal characters in ``user-dirs.dirs`` paths, for example ``$HOME/100% complete``. Lines
follow ``xdg-user-dir`` rules: the last valid line for a directory wins, ``\"`` and other shell escapes inside the
quotes resolve to the escaped character, and platformdirs skips values that are neither ``$HOME``-relative nor absolute.
``XDG_DOCUMENTS_DIR`` in the environment takes precedence over the file and is skipped in the same way unless it is an
absolute path.
On Unix, use percent signs as literal characters in ``user-dirs.dirs`` paths, for example ``$HOME/100% complete``. The
last valid line for a directory wins. Inside double quotes, a backslash is removed only before ``$``, a backtick, ``"``,
or another backslash. Other backslashes, such as ``\q`` in a folder name, remain literal. The ``xdg-user-dir`` utility
also removes a backslash before an ordinary character in a hand-written entry; use ``\\`` for the same result in both.
Platformdirs skips values that are neither ``$HOME``-relative nor absolute. ``XDG_DOCUMENTS_DIR`` in the environment
takes precedence over the file and is skipped in the same way unless it is an absolute path.

.. tab-set::

Expand Down
2 changes: 1 addition & 1 deletion src/platformdirs/unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ def _resolve_user_dirs_value(entry: re.Match[str]) -> str | None:
return None
if entry["quoted"] is not None:
# xdg-user-dirs-update backslash-escapes $, `, " and \ inside the quotes.
value = re.sub(r"\\(.)", r"\1", value)
value = re.sub(r'\\([$`"\\])', r"\1", value)
return prefix + value


Expand Down
23 changes: 23 additions & 0 deletions tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,29 @@ def test_user_dirs_preserves_percent_signs(folder: str, base: str, tmp_path: Pat
assert Unix().user_documents_path == Path(tmp_path if base == "$HOME" else base) / folder


@pytest.mark.parametrize(
("folder", "expected"),
[
pytest.param(r"a\q", r"a\q", id="ordinary-letter"),
pytest.param(r"a\ b", r"a\ b", id="space"),
pytest.param(r"a\-b", r"a\-b", id="hyphen"),
pytest.param(r"a\nb", r"a\nb", id="literal-backslash-n"),
pytest.param(r"a\'b", r"a\'b", id="single-quote"),
pytest.param(r"a\\b", r"a\b", id="escaped-backslash"),
pytest.param(r"a\$b", "a$b", id="escaped-dollar"),
pytest.param(r"a\`b", "a`b", id="escaped-backtick"),
pytest.param(r"a\"b", 'a"b', id="escaped-double-quote"),
pytest.param(r"a\q\\b\$c", r"a\q\b$c", id="mixed-escapes"),
],
)
@pytest.mark.parametrize("base", ["$HOME", "/absolute"])
def test_user_dirs_double_quoted_backslashes(
folder: str, expected: str, base: str, tmp_path: Path, user_dirs_file: Path
) -> None:
user_dirs_file.write_text(f'XDG_DOCUMENTS_DIR="{base}/{folder}"\n', encoding="utf-8")
assert Unix().user_documents_path == Path(tmp_path if base == "$HOME" else base) / expected


@pytest.mark.parametrize(
("content", "expected"),
[
Expand Down
Loading